packages feed

penny-bin 0.12.0.0 → 0.14.0.0

raw patch · 13 files changed

+933/−488 lines, 13 filesdep ~multiargdep ~penny-lib

Dependency ranges changed: multiarg, penny-lib

Files

doc/ledger-grammar.org view
@@ -241,22 +241,18 @@  * FlagNumberPayee -<numberPayee> ::= <number> White* <quotedLvl1Payee>?-<payeeNumber> ::= <quotedLvl1Payee> White* <number>?-<afterFlag> ::= <numberPayee> | <payeeNumber>-<flagFirst> ::= <flag> White* <afterFlag>?+Here we add an additional element to the specification. Square+brackets denote permutations. Elements inside may occur in any order. -<flagPayee> ::= <flag> White* <quotedLvl1Payee>?-<payeeFlag> ::= <quotedLvl1Payee> White* <flag>?-<afterNumber> ::= <flagPayee> | <payeeFlag>-<numberFirst> ::= <number> White* <afterNumber>?+<flagWhite> ::= <flag> White*+<numWhite> ::= <number> White*+<payeeWhite> ::= <payee> White* -<numberFlag> ::= <number> White* <flag>?-<flagNumber> ::= <flag> White* <number>?-<afterPayee> ::= <numberFlag> <flagNumber>-<payeeFirst> ::= <quotedLvl1Payee> White* <afterPayee>?+<flagOpt> ::= <flagWhite>?+<numOpt> ::= <numWhite>?+<payeeOpt> ::= <payeeWhite>? -<flagNumPayee> ::= <flagFirst> | <numberFirst> | <payeeFirst>+<flagNumPayee> ::= [ <flagOpt> <numOpt> <payeeOpt> ]  * Posting 
+ doc/penny-fit-sample.hs view
@@ -0,0 +1,182 @@+{-# OPTIONS_GHC -Wall #-}+-- | This file is a sample of how to configure penny-fit. You will+-- have to adapt it for your own needs. Rename the file to+-- FILENAME.hs, where FILENAME is the name you want for your program+-- (an easy choice is "penny-fit".) Then, compile the file with+--+-- ghc --make FILENAME+--+-- and if all goes well you will have a program to use.+module Main where++import Penny.Brenner+import Data.Version++-- | The Config type configures all the financial institution accounts.+config :: Config+config = Config+  { defaultFitAcct = Nothing+    -- ^ If you have a default financial institution account, use Just+    -- ACCOUNT here. For example, if I wanted to use my amex account+    -- by default, I would put @Just amex@ here. I don't want to use+    -- an account by default; I want to be required to explicitly+    -- state an account, so I put Nothing here.++  , moreFitAccts = [ visa, checking, saving ]+    -- ^ This is a list of financial institution accounts in addition+    -- to the default (if you have one.)+  }++visa :: FitAcct+visa = FitAcct+  { fitAcctName = "visa"+    -- ^ This is the name by which you will identify this account on+    -- the command line (it will be case sensitive). You pick+    -- financial institution accounts by using the @-f@ option.++  , fitAcctDesc = unlines+      [ "Main Visa card account."+      , "To find the downloads, log in and then click on"+      , "Statements --> Download --> Quicken."+      ]+    -- ^ A description of the financial institution account. This+    -- appears when you use the @info@ command, so you can put+    -- information in here like where to find the downloads on the+    -- bank website.++  , dbLocation = "/home/massysett/ledger/visa"+    -- ^ The location of the database of financial institution+    -- postings. You can make this path relative, in which case it is+    -- interpreted relative to the current directory at runtime, or+    -- absolute. No expansion of tildes, environment variables,+    -- etc. is performed.++  , pennyAcct = "Liabilities:Current:Amex"+    -- ^ Postings from this financial institution appear under this+    -- account in your ledger file(s).++  , defaultAcct = "Expenses:Unclassified"+    -- ^ When penny-fit finds a financial institution posting and it+    -- does not have a matching posting in your ledger, it must create+    -- a new transaction with two postings. One posting will be in the+    -- pennyAcct specified above and the other posting will be in this+    -- account.++  , currency = "$"+    -- ^ All postings will be in this currency++  , groupSpecs = GroupSpecs NoGrouping NoGrouping+    -- ^ When penny-fit reprints your ledger, it uses this to+    -- determine how to perform digit grouping for the quantities in+    -- the output. This takes the form+    --+    -- GroupSpecs G1 G2+    --+    -- where G1 and G2 can each be+    --+    -- NoGrouping, GroupLarge, or GroupAll+    --+    -- G1 specifies how to group digits to the left of the decimal+    -- point. G2 specifies how to group digits to the right of the+    -- decimal point. All grouping creates groups of three digits.+    --+    -- NoGrouping means do not do any digit grouping at+    -- all.+    --+    -- GroupLarge means perform digit grouping, but only if the+    -- number to be grouped is greater than 9,999 (if grouping to the+    -- left of the decimal point) or if there are more than 4 decimal+    -- places (if grouping to the right of the decimal point.)+    --+    -- GroupAll means group whenever there are at least four digits to+    -- be grouped.++  , translator = IncreaseIsCredit+    -- ^ Postings from your financial institution are specified in+    -- terms of increases or decreases. Postings in your ledger are+    -- specified in terms of debits or credits. The translator+    -- specifies how to convert a posting from your financial+    -- institution to a posting in the pennyAcct in your ledger. For+    -- deposit accounts (e.g. checking) you will typically use+    -- IncreaseIsDebit; for liability accounts (e.g. credit cards) you+    -- will typically use IncreaseIsCredit.++  , side = CommodityOnLeft+    -- ^ When penny-fit creates new postings it must put the commodity+    -- either to the left of the quantity or to the right of the+    -- quantity. Accordingly your choices here are CommodityOnLeft or+    -- CommodityOnRight.++  , spaceBetween = NoSpaceBetween+    -- ^ When penny-fit creates new postings it must decide whether to+    -- put a space between the commodity and the quantity. Accordingly+    -- your choices here are SpaceBetween or NoSpaceBetween.++  , parser = ofxParser+    -- ^ This determines how to parse the data you have+    -- downloaded. Currently there is only one parser, which handles+    -- OFX data. Many financial institutions provide OFX data so this+    -- will get you a long way. If you want to write additional+    -- parsers you can provide your own function of this type (perhaps+    -- your bank only provides CSV files, for example.)++  , toLincolnPayee = usePayeeOrDesc+  -- ^ Sometimes the financial institution provides Payee information,+  -- sometimes it does not. Sometimes the Desc might have additional+  -- information that you might want to remove. This function can be+  -- used to do that. The resulting Lincoln Payee is used for any+  -- transactions that are created by the merge command. The resulting+  -- payee is also used when comparing new financial institution+  -- postings to already existing ledger transactions in order to+  -- guess at which payee and accounts to create in the transactions+  -- created by the merge command.+  --+  -- 'usePayeeOrDesc' simply uses the payee if it is available;+  -- otherwise, it uses the description. (Many banks provide+  -- descriptions only and do not provide separate payee information.)+  }++checking :: FitAcct+checking = FitAcct+  { fitAcctName = "checking"+  , fitAcctDesc = "Main checking account."+  , dbLocation = "/home/massysett/ledger/checking"+  , pennyAcct = "Assets:Current:Checking"+  , defaultAcct = "Expenses:Unclassified"+  , currency = "$"+  , groupSpecs = GroupSpecs NoGrouping NoGrouping+  , translator = IncreaseIsDebit+  , side = CommodityOnLeft+  , spaceBetween = NoSpaceBetween+  , toLincolnPayee = usePayeeOrDesc+  , parser = ofxParser+  }++saving :: FitAcct+saving = FitAcct+  { fitAcctName = "saving"+  , fitAcctDesc = "Main saving account."+  , dbLocation = "/home/massysett/ledger/saving"+  , pennyAcct = "Assets:Current:Checking:Omari"+  , defaultAcct = "Expenses:Unclassified"+  , currency = "$"+  , groupSpecs = GroupSpecs NoGrouping NoGrouping+  , translator = IncreaseIsDebit+  , side = CommodityOnLeft+  , spaceBetween = NoSpaceBetween+  , toLincolnPayee = usePayeeOrDesc+  , parser = ofxParser+  }++-- Leave things below this line alone (unless you know what you're+-- doing of course.)++-- brennerMain requires that you supply a version. You can edit this+-- as you see fit, or use the version that Cabal supplies.+version :: Version+version = Version [1] []++-- | Always leave these two lines the same (unless you know what you+-- are doing of course).+main :: IO ()+main = brennerMain version config
+ doc/penny-ofx.ini view
@@ -0,0 +1,70 @@+# Sample configuration file for penny-ofx++# Optional default financial institution account. If you do not+# specify a default account, penny-ofx will require that you specify+# one using the -f option every time you run penny-ofx.+default_fit_acct = bigbank++# Example: a credit card from Bigbank. All of the fields shown are+# required. You can configure multiple financial institution accounts.+[bigbank]++# File in which to store database information for this account. You+# must use a different file for each account. Therefore, be careful if+# you cut and paste when you create new accounts in this file, as you+# must change the filename here.+file = /home/massysett/ledger/bigbank.bin++# Postings from this account appear under this account in your ledger+# file(s).+penny_acct = Liabilities:Current:Bigbank++# When penny-fit finds a financial institution posting and it+# does not have a matching posting in your ledger, it must create+# a new transaction with two postings. One posting will be in the+# pennyAcct specified above and the other posting will be in this+# account.+default_acct = Expenses:Unclassified++# All postings will be in this currency+currency = $++# The next two options control digit grouping when penny-ofx reprints+# your ledger. groupLeft and groupRight control whether digits are+# grouped to the left and to the right of the radix point,+# respectively. Available choices are:+#+# none - do not group any digits+#+# large - group only if the number to be grouped is greater than 9,999+# (if grouping to the left of the decimal point) or if there are more+# than 4 decimal places (if grouping to the right of the decimal+# point.)+#+# all - group whenever there are at least four digits to be grouped.+group_left = all+group_right = none++# Postings from your financial institution are specified in terms of+# increases or decreases. Postings in your ledger are specified in+# terms of debits or credits. The translator specifies how to convert+# a posting from your financial institution to a posting in the+# pennyAcct in your ledger.+increase_is = Credit++# When penny-fit creates new postings it must put the commodity+# either to the left of the quantity or to the right of the+# quantity.+commodity_on = Left++# When penny-fit creates new postings it must decide whether to+# put a space between the commodity and the quantity.+space_between = False++# Give some helpful information here about where to find the file on+# your bank website. You can use multiline strings, as shown, by+# indenting subsequent lines.+more_info = Statements from bigbank.+  Bigbank makes its statements available by clicking on+  "Account Info", then logging in, then clicking on+  "Statements".
− man/penny-fit.1
@@ -1,245 +0,0 @@-.TH penny-fit 7--.SH NAME-penny-fit - Penny financial institution statements parser--.SH SYNOPSIS-.B penny-fit-[global-options] COMMAND [local-options] ARGS--.SH DESCRIPTION-.B penny-fit-works with data you have downloaded from your financial-institution. It parses statements that you have downloaded and adds-transactions from the statements to your ledger, skipping those that-have already been added. It also helps you reconcile your ledger with-financial institution statements.--First you will have to configure and compile a-.B penny-fit-binary. You can use the-.B penny-fit-sample.hs-file in the-.B penny-bin-package as an example. The comments in that file should help you get-started.--.SH IMPORTING--To use-.BR penny-fit ,-first you will download the appropriate data from your financial-institution and place it in a file.-.I penny-fit -h-will tell give you a little more information about the place to look-on your institution's web site to download the data. Currently-.B penny-fit-can handle Bank of America and American Express data; maybe you can-add support for more institutions.--Then, run-.IR "penny-fit -f ACCOUNT import FILENAME" .-The first time you run this command, you will have to add the-.I --new-option after the-.I import-command, which allows the creation of a new database. Without this-option, if the database is not found,-.B penny-fit-quits with an error message. The ACCOUNT must be a financial-institution account that you configured in your-.B penny-fit-binary (if you configured a default account and you want to use that,-you can omit the-.I -f-option.) The FILENAME is the location of the data that you just-downloaded.--The-.I import-command will examine the data that you downloaded. Using the unique-identifiers already assigned to each posting by your financial-institution, the-.I import-command determines whether you have already downloaded each particular-posting. If the posting is new,-.I import-assigns a different, unique number to the posting. This is called a-.IR U-number .-The U-number allows you to uniquely identify each posting that you-download. The data from the financial institution, along with the-U-number, is added to a database at the location specified in your-configuration.-.I import-automatically skips postings that have already been processed, so you-do not have to worry about importing duplicate postings.--.SH MERGING--Next you will want to merge new postings into your ledger. Do this by-running-.I penny-fit -f ACCOUNT merge LEDGER_FILE...-where LEDGER_FILE is one or more filenames for your ledger files.-.I merge-examines your ledgers to see if each of the postings in the database-for this financial institution is represented in your ledger. To do-this it looks at the postings in the-.I pennyAcct-specified in your configuration. For each U-number in the database,-.I merge-sees if there is a posting in the-.I pennyAcct-with a tag bearing the U-number (e.g. if the U-number is 5, it looks-for the tag-.IR U5 .)-If a posting has more than one U-number tag, only the first is used;-the others are ignored. If such a posting is found,-.I merge-moves on to the next U-number in the database.--If no matching posting is found for a U-number,-.I merge-sees if there is a matching posting that does-.I not-have a U-number tag. If there is a posting in the-.I pennyAcct-that has the same quantity and date as the financial institution-posting,-.I merge-will then examine the debit or credit of the ledger posting. This-table describes whether-.I penny-fit-will find a match:--.TS-tab(:);-l l l l-- - - --l l l l.-T{-If the financial institution posting is a-T}:T{-and translator is-T}:T{-and the ledger posting is a-T}:T{-then is there a match?-T}-increase:IncreaseIsDebit:debit:Yes-increase:IncreaseIsDebit:credit:No-increase:IncreaseIsCredit:debit:No-increase:IncreaseIsCredit:credit:Yes-decrease:IncreaseIsDebit:debit:No-decrease:IncreaseIsDebit:credit:Yes-decrease:IncreaseIsCredit:debit:Yes-decrease:IncreaseIsCredit:credit:No-.TE--If-.B penny-fit-finds a match for a financial institution posting in this way, then it-will assign a new U-number tag to the posting. If-.B penny-fit-does not find a match, then it will create an entirely new transaction-and append it to the end of your ledger.--If it is creating an entirely new transaction,-.B penny-fit-will attempt to give the new transaction the same account and payee-information that you have used for similar transactions in the-past. To do this,-.B penny-fit-will first search through the database to find the most recent-financial institution posting that has the same payee as the one of-the new transaction. If one is found,-.B penny-fit-then searches through the postings in your ledger file to find the one-that has the same U-number and account as the old financial-institution posting. If it is found,-.B penny-fit-will assign the payee name found on the posting in the ledger to the-new posting. Also, if the posting found in the ledger has exactly one-sibling posting,-.B penny-fit-will assign the same account name from that sibling to the new-sibling.--You can turn off this automatic assignment of information by using the-.I --no-auto-or -.I -n-option to the-.I merge-command.--The result of-.I merge-is printed to standard output. You will probably want to redirect it-to a new file. Use-.BR diff (1)-or-.BR penny-diff (1)-to see what changes-.I merge-made. Typically you will need to edit the output somewhat.--.SH RECONCILING-Next you may wish to reconcile your ledger with your financial-institution data (that is, "balance the checkbook".) Typically the-most time-consuming part of this process is finding the postings in-your ledger that match the postings on your bank statement.-.B penny-fit clear-will help with this, dramatically speeding up the process.  To do-this, download data from your financial institution that corresponds-to the data that is covered within the current statement period. Run-.B penny-fit import-and-.B penny-fit merge-as described above. Then run--.EX-penny-fit -f ACCOUNT clear FIT_FILE LEDGER_FILE...-.EE--where FIT_FILE is the data file you downloaded from your financial-institution, and LEDGER_FILE contains your ledger data. The-.I clear-command will mark as cleared (that is, assign a-.I C-flag to) all postings in your LEDGER_FILEs that correspond to one of-the postings in the FIT_FILE. It does this by matching the U-number-tags on your postings to the U-numbers in the database. If a posting-has more than one U-number tag, only the first is used; the others are-ignored. The results are printed to standard output; you will probably-want to redirect this to a file. Once you have verified that things-are as they should be, you can use-.BR penny-reconcile (1)-to mark the cleared postings as reconciled.-.BR penny-basics (7)-has more details on how to use-.B penny-when reconciling a financial institution statement.--.SH OTHER COMMANDS-The-.I database-command prints the database for a particular financial institution to-standard output in human-readable form (the database unfortunately is-not in plain human-readable text.) For instance you might use this to-see what U-number is assigned to a particular financial institution-posting.--The-.I print-command parses a downloaded file of financial institution data and-prints the result to standard output. This is useful for seeing the-contents of a financial institution data file, or for testing new-parsers.--.SH BUGS-To quote another man page: "Bugs? You must be kidding, there are no-bugs in this software. But if we happen to be wrong, send us an email-with as much detail as possible to" omari@smileystation.com.--.SH SEE ALSO-.BR penny-suite (7)
+ man/penny-fit.7 view
@@ -0,0 +1,374 @@+.TH penny-fit 7+.+.SH NAME+penny-fit - Penny financial institution statements parser+.+.SH SYNOPSIS+.B penny-fit+[global-options] COMMAND [local-options] ARGS+.+.SH DESCRIPTION+.+.B penny-fit+works with data you have downloaded from your financial+institution.+.+It parses statements that you have downloaded and adds+transactions from the statements to your ledger, skipping those that+have already been added.+.+It also helps you reconcile your ledger with+financial institution statements.+.+.P+First you will have to configure and compile a+.B penny-fit+binary.+.+You can use the+.B penny-fit-sample.hs+file in the+.I doc+directory of the+.B penny-bin+package as an example.+.+Alternatively, if you know your way around Haskell, see the Haddock+documentation for the+.I Penny.Brenner+module.+.+The comments in that file should help you get+started.+.+.P+Currently Penny includes a parser for Open Financial+Exchange, or OFX, data.+.+Many banks make information available in this+format, as Quicken and the now-defunct Microsoft Money both support+the format.+.+.SH IMPORTING+.+To use+.BR penny-fit ,+first you will download the appropriate data from your financial+institution and place it in a file.+.+.I penny-fit info+will tell give you a little more information about the place to look+on your institution's web site to download the data, if you have+configured that information in your binary.+.+.P+Then, run+.IR "penny-fit -f ACCOUNT import FILENAME" .+The first time you run this command, you will have to add the+.I --new+option after the+.I import+command, which allows the creation of a new database.+.+Without this+option, if the database is not found,+.B penny-fit+quits with an error message.+.+The ACCOUNT must be a financial+institution account that you configured in your+.B penny-fit+binary (if you configured a default account and you want to use that,+you can omit the+.I -f+option).+.+The FILENAME is the location of the data that you just+downloaded.+.+.P+The+.I import+command will examine the data that you downloaded.+.+Using the unique+identifiers already assigned to each posting by your financial+institution, the+.I import+command determines whether you have already downloaded each particular+posting.+.+If the posting is new,+.I import+assigns a different, unique number to the posting.+.+This is called a+.IR U-number .+The U-number allows you to uniquely identify each posting that you+download.+.+The data from the financial institution, along with the+U-number, is added to a database at the location specified in your+configuration.+.I import+automatically skips postings that have already been processed, so you+do not have to worry about importing duplicate postings.+.+.SH MERGING+.+Next you will want to merge new postings into your ledger.+.+Do this by+running+.I penny-fit -f ACCOUNT merge LEDGER_FILE...+where LEDGER_FILE is one or more filenames for your ledger files.+.+.I merge+examines your ledgers to see if each of the postings in the database+for this financial institution is represented in your ledger.+.+To do+this it looks at the postings in the+.I pennyAcct+specified in your configuration.+.+For each U-number in the database,+.I merge+sees if there is a posting in the+.I pennyAcct+with a tag bearing the U-number (e.g. if the U-number is 5, it looks+for the tag+.IR U5 ).+If a posting has more than one U-number tag, only the first is used;+the others are ignored.+.+If such a posting is found,+.I merge+moves on to the next U-number in the database.+.+If no matching posting is found for a U-number,+.I merge+sees if there is a matching posting that does+.I not+have a U-number tag.+.+If there is a posting in the+.I pennyAcct+that has the same quantity and date as the financial institution+posting,+.I merge+will then examine the debit or credit of the ledger posting.+.+.P+This table describes whether+.I penny-fit+will find a match:+.+.TS+tab(:);+l l l l+- - - -+l l l l.+T{+If the financial institution posting is a+T}:T{+and translator is+T}:T{+and the ledger posting is a+T}:T{+then is there a match?+T}+increase:IncreaseIsDebit:debit:Yes+increase:IncreaseIsDebit:credit:No+increase:IncreaseIsCredit:debit:No+increase:IncreaseIsCredit:credit:Yes+decrease:IncreaseIsDebit:debit:No+decrease:IncreaseIsDebit:credit:Yes+decrease:IncreaseIsCredit:debit:Yes+decrease:IncreaseIsCredit:credit:No+.TE+.+If+.B penny-fit+finds a match for a financial institution posting in this way, then it+will assign a new U-number tag to the posting.+.+If+.B penny-fit+does not find a match, then it will create an entirely new transaction+and append it to the end of your ledger.+.+.P+If it is creating an entirely new transaction,+.B penny-fit+will attempt to give the new transaction the same account and payee+information that you have used for similar transactions in the+past.+.+To do this,+.B penny-fit+will first search through the database to find the most recent+financial institution posting that has the same payee as the one of+the new transaction. If one is found,+.B penny-fit+then searches through the postings in your ledger file to find the one+that has the same U-number and account as the old financial+institution posting.+.+If it is found,+.B penny-fit+will assign the payee name found on the posting in the ledger to the+new posting.+.+Also, if the posting found in the ledger has exactly one+sibling posting,+.B penny-fit+will assign the same account name from that sibling to the new+sibling.+.+You can turn off this automatic assignment of information by using the+.I --no-auto+or +.I -n+option to the+.I merge+command.+.+.P+The result of+.I merge+is printed to standard output, unless you use the+.I --output FILENAME+or+.I -o FILENAME+option, in which case the output is sent to+.IR FILENAME .+.+You can use multiple+.I -o+options.+.+To explicitly send output to standard output, use+.IR "-o -" .+.+Use+.BR diff (1)+or+.BR penny-diff (1)+to see what changes+.I merge+made.+.+Typically you will need to edit the output somewhat.+.+.SH RECONCILING+.+Next you may wish to reconcile your ledger with your financial+institution data (that is, "balance the checkbook").+.+Typically the+most time-consuming part of this process is finding the postings in+your ledger that match the postings on your bank statement.+.+.B penny-fit clear+will help with this, dramatically speeding up the process.+.+To do+this, download data from your financial institution that corresponds+to the data that is covered within the current statement period.+.+Run+.B penny-fit import+and+.B penny-fit merge+as described above. Then run+.+.P+.EX+penny-fit -f ACCOUNT clear FIT_FILE LEDGER_FILE...+.EE+.+.P+where FIT_FILE is the data file you downloaded from your financial+institution, and LEDGER_FILE contains your ledger data.+.+The+.I clear+command will mark as cleared (that is, assign a+.I C+flag to) all postings in your LEDGER_FILEs that correspond to one of+the postings in the FIT_FILE.+.+It does this by matching the U-number+tags on your postings to the U-numbers in the database.+.+If a posting+has more than one U-number tag, only the first is used; the others are+ignored.+.+.P+As with the +.I merge+command, the results are printed to standard output unless you use the+.I --output FILENAME+or+.I -o FILENAME+option.+.+Once you have verified that things+are as they should be, you can use+.BR penny-reconcile (1)+to mark the cleared postings as reconciled.+.+.BR penny-basics (7)+has more details on how to use+.B penny+when reconciling a financial institution statement.+.+.SH OTHER COMMANDS+.+The+.I database+command prints the database for a particular financial institution to+standard output in human-readable form (the database unfortunately is+not in plain human-readable text).+.+For instance you might use this to+see what U-number is assigned to a particular financial institution+posting.+.+.P+The+.I print+command parses a downloaded file of financial institution data and+prints the result to standard output.+.+This is useful for seeing the+contents of a financial institution data file, or for testing new+parsers.+.+.P+Every+.B penny-fit+command has a+.I -h+and a+.I --help+option.+.+There is also a global+.I --help+option, as in+.IR "penny-fit --help" .+.+.SH BUGS+To quote another man page: "Bugs?+.+You must be kidding, there are no+bugs in this software.+.+But if we happen to be wrong, send us an email+with as much detail as possible to" omari@smileystation.com.+.+.SH SEE ALSO+.BR penny-suite (7)
man/penny-reconcile.1 view
@@ -1,12 +1,13 @@ .TH penny-reconcile 1-+. .SH NAME penny-reconcile - mark cleared postings as reconciled-+. .SH SYNOPSIS .B penny-reconcile+[options] FILE...-+. .SH DESCRIPTION Finds all postings in the input ledger files whose flag is exactly one letter: the letter@@ -18,25 +19,47 @@ postings in your ledger that match a posting on your bank statement with a .I C-flag. Because only postings from the current statement will be marked with a+flag.+.+Because only postings from the current statement will be marked with a .I C flag, it is easier to use .BR penny (1)-to list only the postings that you have just cleared.  Then, after+to list only the postings that you have just cleared.+.+Then, after ensuring that the statement is properly reconciled, .B penny-reconcile will automatically mark all the posts reconciled.-+. If no .IR FILE ", or " FILE " is " - , read standard input.-+.+.SH OPTIONS+.+.TP+.IR "--output FILENAME", " -o FILENAME"+.+send output to+.I FILENAME+instead of standard output.+.+You can use multiple+.I --output+options; to explicitly print to standard output, use+.IR "--output -" .+. .SH VERSUS sed(1) OR YOUR TEXT EDITOR You could do this with .BR sed (1)-or your text editor. Unlike those programs,+or your text editor.+.+Unlike those programs, .B penny-reconcile-knows the structure of a ledger file. So+knows the structure of a ledger file.+.+So .B penny-reconcile will not, for example, change the text .IR [C] " to " [R]@@ -44,9 +67,11 @@ .I [C] appears within a comment, while a naive .BR sed (1)-script would do so. This would happen only rarely though, so you might+script would do so.+.+This would happen only rarely though, so you might be just fine using a query-replace function in your text editor.-+. Also, .B penny-reconcile will tidy up your ledger file--that is, it might rearrange or delete@@ -55,16 +80,16 @@ This might be good or bad. .BR sed (1) or your text editor, on the other hand, will not do this.-+. .SH EXIT STATUS 0 if everything went fine; some other value if something went wrong (e.g. a ledger file could not be parsed.)-+. .SH BUGS Please report bugs in the program or documentation to .MT omari@smileystation.com Omari Norman. .ME-+. .SH SEE ALSO .BR penny-suite (7)
man/penny-reprint.1 view
@@ -1,19 +1,22 @@ .TH penny-reprint 1-+. .SH NAME penny-reprint - read and reprint Penny ledger-+. .SH SYNOPSIS .B penny-reprint+[options] .I FILE...-+. .SH DESCRIPTION-+. Reads the Penny ledger(s) you specify and prints them to standard-output, in a (hopefully) more tidy format. All comments are retained,+output, in a (hopefully) more tidy format.+.+All comments are retained, but blank lines and insignicant whitespace within the transactions (such as the amount of whitespace between multiple tags) are ignored.-+. If no .IR FILE , or@@ -21,16 +24,30 @@ is .IR - , read standard input.-+.+.SH OPTIONS+.+.TP+.IR "--output FILENAME", " -o FILENAME"+.+send output to+.I FILENAME+instead of standard output.+.+You can use multiple+.I --output+options; to explicitly print to standard output, use+.IR "--output -" .+. .SH EXIT STATUS 0 if everything went fine; another value if there were problems (such as inability to read a ledger file.)-+. .SH BUGS Please report any bugs in the program or documentation to .MT omari@smileystation.com Omari Norman .ME-+. .SH SEE ALSO .BR penny-suite (7)
man/penny-suite.7 view
@@ -1,136 +1,136 @@ .TH penny-suite 7-+. .SH NAME penny-suite - extensible double-entry accounting system-+. .SH DESCRIPTION-+. This manual page lists all the different components of Penny and also catalogues all the documentation files and manual pages that are available.-+. .SH PENNY PROGRAMS-+. Penny consists of many programs. Each has its own manual page.-+. .TP .BR penny (1)-+. reports on postings in your ledger file-+. .TP .BR penny-selloff (1)-+. calculate capital gains and losses on commodity sales-+. .TP .BR penny-diff (1)-+. show differences between ledger files-+. .TP .BR penny-reprint (1)-+. tidy up a ledger file, retaining comments-+. .TP .BR penny-reconcile (1)-+. marks cleared postings as reconciled-+. .SH PENNY MANUAL PAGES-+. In addition to the manual pages shown above, a few more overview man pages are available.-+. .TP .BR penny-basics (7)-+. getting started with .B penny-+. .TP .BR penny-examples (7)-+. more examples of .B penny usage-+. .TP .BR penny-commodities (7)-+. tracking multiple commodities, such as stocks, with Penny-+. .TP .BR penny-fit (1)-+. describes how you might be able to create a program that automatically parses downloaded statements from your financial institution and merges the resulting postings into your ledger file.-+. .TP .BR penny-custom (7)-+. how to make a custom \fBpenny\fR program with your own settings-+. .SH SAMPLE FILES-+. There are many files available filled with sample data. They are in the .I examples directory of the .B penny-bin tarball.-+. .TP .BR starter.pny-+. describes the basics of the Penny ledger file format, and contains sample data. The .BR penny-basics (7) manual page uses the sample data from this file.-+. .TP .BR stocks.pny-+. shows how to use Penny to track multiple commodities, like stocks-+. .TP .BR stocks-realized.pny-+. shows an example of the results of using .BR penny-selloff (1)--+.+. .SH TEXT FILES-+. Much documentation is available only in plain-text form. These files are in the .B doc directory of the .B penny-bin tarball.-+. .TP .BR amex-file-format.org-+. the file format American Express uses for its downloadable data-+. .TP .BR bofa-file-format.org-+. the file format Bank of America uses for its downloadable data-+. .TP .BR dependencies.dot-+. the dependencies between the various parts of the Penny library-+. .TP .BR ledger-grammar.org-+. an EBNF grammar for the ledger file format-+. .SH HADDOCK-+. Feel free to examine the Haskell source code of the Penny library, which also contains Haddock documentation markup.
penny-bin.cabal view
@@ -1,5 +1,5 @@ Name: penny-bin-Version: 0.12.0.0+Version: 0.14.0.0 Cabal-version: >=1.8 Build-Type: Simple License: BSD3@@ -53,6 +53,13 @@    . +  * 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.    .@@ -166,6 +173,8 @@   , README   , doc/*.org   , doc/*.dot+  , doc/*.ini+  , doc/*.hs   , examples/*.pny   , man/*.1   , man/*.7@@ -178,7 +187,7 @@ Executable penny   Build-depends:     base ==4.*,-    penny-lib ==0.12.*+    penny-lib ==0.14.*    Main-is: penny-main.hs   Other-modules: Paths_penny_bin@@ -192,13 +201,13 @@ Executable penny-selloff   Build-depends:     base == 4.*,-    penny-lib ==0.12.*,+    penny-lib ==0.14.*,     explicit-exception ==0.1.*,     containers ==0.5.*,     semigroups ==0.9.*,     text ==0.11.*,     parsec ==3.1.*,-    multiarg ==0.14.*,+    multiarg ==0.16.*,     transformers ==0.3.*    Main-is: penny-selloff.hs@@ -212,9 +221,9 @@ Executable penny-diff   Build-depends:     base ==4.*,-    penny-lib ==0.12.*,+    penny-lib ==0.14.*,     text ==0.11.*,-    multiarg ==0.14.*,+    multiarg ==0.16.*,     explicit-exception == 0.1.*    Main-is: penny-diff.hs@@ -229,8 +238,8 @@ Executable penny-reprint   Build-depends:       base ==4.*-    , multiarg ==0.14.*-    , penny-lib ==0.12.*+    , multiarg ==0.16.*+    , penny-lib ==0.14.*     , pretty-show ==1.5.*     , text ==0.11.* @@ -243,9 +252,9 @@ Executable penny-reconcile   Build-depends:       base ==4.*-    , penny-lib ==0.12.*+    , penny-lib ==0.14.*     , text ==0.11.*-    , multiarg ==0.14.*+    , multiarg ==0.16.*     , explicit-exception ==0.1.*    main-is: penny-reconcile.hs@@ -278,3 +287,4 @@ Flag build-reconcile   Description: Build the penny-reconcile executable   Default: True+
penny-diff.hs view
@@ -1,16 +1,16 @@ module Main where -import qualified Control.Exception as CEx-import qualified Control.Monad.Exception.Synchronous as Ex+import Control.Arrow (first, second) import Data.Either (partitionEithers) import Data.Maybe (fromJust) import Data.List (deleteFirstsBy) import qualified System.Console.MultiArg as M import qualified Penny.Liberty as Ly import qualified Penny.Lincoln as L-import qualified Penny.Lincoln.Predicates as P+import Penny.Lincoln ((==~)) import qualified Penny.Copper as C import qualified Penny.Copper.Render as CR+import qualified Penny.Steel.Sums as S import Data.Maybe (mapMaybe) import qualified Data.Text as X import qualified Data.Text.IO as TIO@@ -59,26 +59,27 @@   deriving (Eq, Show)  -- | All possible items, but excluding blank lines.-data NonBlankItem-  = Transaction L.Transaction-  | Price L.PricePoint-  | Comment C.Comment-  deriving (Eq, Show)+type NonBlankItem =+  S.S3 L.Transaction L.PricePoint C.Comment +removeMeta+  :: L.Transaction+  -> (L.TopLineCore, L.Ents L.PostingCore)+removeMeta+  = first L.tlCore+  . second (fmap L.pdCore)+  . L.unTransaction+ clonedNonBlankItem :: NonBlankItem -> NonBlankItem -> Bool clonedNonBlankItem nb1 nb2 = case (nb1, nb2) of-  (Transaction t1, Transaction t2) -> P.clonedTransactions t1 t2-  (Price p1, Price p2) -> p1 == p2-  (Comment c1, Comment c2) -> c1 == c2+  (S.S3a t1, S.S3a t2) -> removeMeta t1 ==~ removeMeta t2+  (S.S3b p1, S.S3b p2) -> p1 ==~ p2+  (S.S3c c1, S.S3c c2) -> c1 == c2   _ -> False -toNonBlankItem :: C.Item -> Maybe NonBlankItem-toNonBlankItem i = case i of-  C.Transaction t -> Just (Transaction t)-  C.PricePoint p -> Just (Price p)-  C.IComment c -> Just (Comment c)-  _ -> Nothing-+toNonBlankItem :: C.LedgerItem -> Maybe NonBlankItem+toNonBlankItem = S.caseS4 (Just . S.S3a) (Just . S.S3b) (Just . S.S3c)+                          (const Nothing)  showLineNum :: File -> Int -> X.Text showLineNum f i = X.pack ("\n" ++ arrow ++ " " ++ show i ++ "\n")@@ -97,15 +98,16 @@   -> File   -> L.Transaction   -> Maybe X.Text-renderTransaction gs f t = fmap addHeader $ CR.transaction gs t+renderTransaction gs f t = fmap addHeader $ CR.transaction gs (noMeta t)   where-    (L.Family tl _ _ _) = L.unTransaction t-    lin = case L.tMemo tl of-      Nothing -> L.unTopLineLine . fromJust-                 . L.tTopLineLine $ tl-      Just _ -> L.unTopMemoLine . fromJust-                . L.tTopMemoLine $ tl+    lin = case L.tMemo . L.tlCore . fst . L.unTransaction $ t of+      Nothing -> L.unTopLineLine . L.tTopLineLine . fromJust+                 . L.tlFileMeta . fst . L.unTransaction $ t+      Just _ -> L.unTopMemoLine . fromJust . L.tTopMemoLine . fromJust+                . L.tlFileMeta . fst . L.unTransaction $ t     addHeader x = (showLineNum f lin) `X.append` x+    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@@ -114,16 +116,14 @@     addHeader x = (showLineNum f lin) `X.append` x  renderNonBlankItem :: CR.GroupSpecs -> File -> NonBlankItem -> Maybe X.Text-renderNonBlankItem gs f n = case n of-  Transaction t -> renderTransaction gs f t-  Price p -> renderPrice gs f p-  Comment c -> CR.comment c+renderNonBlankItem gs f =+  S.caseS3 (renderTransaction gs f) (renderPrice gs f) CR.comment  runPennyDiff :: CR.GroupSpecs -> IO () runPennyDiff co = do   (f1, f2, dts) <- parseCommandLine-  l1 <- parseFile f1-  l2 <- parseFile f2+  l1 <- C.open [f1]+  l2 <- C.open [f2]   let (r1, r2) = doDiffs l1 l2   showDiffs co dts (r1, r2)   case (r1, r2) of@@ -167,36 +167,22 @@ -- but not in file2, and snd p is items that appear in file2 but not -- in file1. doDiffs-  :: C.Ledger-  -> C.Ledger+  :: [C.LedgerItem]+  -> [C.LedgerItem]   -> ([NonBlankItem], [NonBlankItem]) doDiffs l1 l2 = (r1, r2)   where-    mkNbList = mapMaybe toNonBlankItem . C.unLedger+    mkNbList = mapMaybe toNonBlankItem     (nb1, nb2) = (mkNbList l1, mkNbList l2)     df = deleteFirstsBy clonedNonBlankItem     (r1, r2) = (nb1 `df` nb2, nb2 `df` nb1) -parseFile :: String -> IO C.Ledger-parseFile s = do-  eiTxt <- CEx.try $ TIO.readFile s-  txt <- case eiTxt of-    Left e -> failure (show (e :: IOError))-    Right g -> return g-  let fn = L.Filename . X.pack $ s-      c = C.FileContents txt-      parsed = C.parse [(fn, c)]-  case parsed of-    Ex.Exception e -> failure (show e)-    Ex.Success g -> return g-- -- | Returns a tuple with the first filename, the second filename, and -- an indication of which differences to show. parseCommandLine :: IO (String, String, DiffsToShow) parseCommandLine = do   as <- M.simpleWithHelp help M.Intersperse allOpts-        (Right . Filename)+        (return . Right . Filename)   let (doVer, args) = partitionEithers as       toFilename a = case a of         Filename s -> Just s
penny-reconcile.hs view
@@ -2,40 +2,40 @@  import Data.Maybe (fromMaybe, fromJust) import qualified Data.Text as X-import qualified Data.Text.IO as TIO import Control.Monad (guard) import qualified Penny.Copper as C import qualified Penny.Lincoln as L import qualified Penny.Liberty as Ly+import qualified Penny.Steel.Sums as S import qualified System.Console.MultiArg as MA import qualified Paths_penny_bin as PPB   -- | Changes a posting to mark it reconciled, if it was already marked -- as cleared.-changePosting :: L.Posting -> L.PostingChangeData-changePosting p = fromMaybe L.emptyPostingChangeData $ do-  fl <- L.pFlag p+changePosting :: L.PostingData -> L.PostingData+changePosting p = fromMaybe p $ do+  let c = L.pdCore p+  fl <- L.pFlag c   guard (L.unFlag fl == X.singleton 'C')   let fl' = L.Flag . X.singleton $ 'R'-  return $ L.emptyPostingChangeData { L.pcFlag = Just (Just fl') }+      c' = c { L.pFlag = Just fl' }+  return p { L.pdCore = c' }  -- | Changes a TopLine to mark it as reconciled, if it was already -- marked as cleared.-changeTopLine :: L.TopLine -> L.TopLineChangeData-changeTopLine t = fromMaybe L.emptyTopLineChangeData $ do-  fl <- L.tFlag t+changeTopLine :: L.TopLineData -> L.TopLineData+changeTopLine t = fromMaybe t $ do+  let c = L.tlCore t+  fl <- L.tFlag c   guard (L.unFlag fl == X.singleton 'C')   let fl' = L.Flag . X.singleton $ 'R'-  return $ L.emptyTopLineChangeData { L.tcFlag = Just (Just fl') }+      c' = c { L.tFlag = Just fl' }+  return t { L.tlCore = c' }  changeTransaction :: L.Transaction -> L.Transaction-changeTransaction t =-  let fam = L.mapParent changeTopLine-            . L.mapChildren changePosting-            . L.unTransaction-            $ t-  in L.changeTransaction fam t+changeTransaction (L.Transaction (tl, es)) =+  L.Transaction (changeTopLine tl, fmap changePosting es)  help :: String -> String help pn = unlines@@ -48,6 +48,10 @@   , "changed."   , ""   , "Options:"+  , "  --output FILENAME, -o FILENAME"+  , "    send output to FILENAME rather than standard output"+  , "    (multiple -o options are allowed; use \"-\" for standard"+  , "     output)"   , "  -h, --help - Show help and exit."   , "  --version  - Show version and exit"   ]@@ -55,27 +59,24 @@ groupSpecs :: C.GroupSpecs groupSpecs = C.GroupSpecs C.NoGrouping C.NoGrouping --- | The first element if the pair is a no-op if the user does not--- need to see the version, or an IO action to print the version if--- the user wants to see it. The second element is the list of command--- line arguments.-type Opts = (IO (), [String])--allOpts :: [MA.OptSpec (Opts -> Opts)]-allOpts = [ fmap (\act (_, ss) -> (act, ss)) $-            Ly.version PPB.version-          ]+type ShowVer = IO ()+type Printer = X.Text -> IO ()+type PosArg = String+type Arg = S.S3 ShowVer Printer PosArg -posArg :: String -> Opts -> Opts-posArg s (a, ss) = (a, s:ss)+allOpts :: [MA.OptSpec Arg]+allOpts =+  [ fmap S.S3a $ Ly.version PPB.version+  , fmap S.S3b Ly.output+  ]  main :: IO () main = do-  as <- MA.simpleWithHelp help MA.Intersperse allOpts posArg-  let opts = foldr ($) (return (), []) as-  fst opts-  led <- C.open . snd $ opts-  let led' = C.mapLedger (C.mapItem id id changeTransaction) led-      rend = fromJust $ C.ledger groupSpecs led'-  TIO.putStr rend+  as <- MA.simpleWithHelp help MA.Intersperse allOpts (fmap return S.S3c)+  let (showVer, printers, posArgs) = S.partitionS3 as+  sequence_ showVer+  led <- C.open posArgs+  let led' = map (S.mapS4 changeTransaction id id id) led+      rend = fromJust $ mapM (C.item groupSpecs) (map C.stripMeta led')+  let txt = X.concat rend in txt `seq` (Ly.processOutput printers txt) 
penny-reprint.hs view
@@ -3,8 +3,9 @@ import qualified Penny.Copper as C import qualified Penny.Copper.Render as R import qualified Penny.Liberty as Ly-import qualified Data.Text.IO as TIO+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 @@ -21,6 +22,10 @@   , "Result is printed to standard output."   , ""   , "Options:"+  , "  --output FILENAME, -o FILENAME"+  , "    send output to FILENAME rather than standard output"+  , "    (multiple -o options are allowed; use \"-\" for standard"+  , "     output)"   , "  --help, -h - show help and exit"   , "  --version  - show version and exit"   ]@@ -29,21 +34,25 @@ groupSpecs = R.GroupSpecs R.NoGrouping R.NoGrouping  type MaybeShowVer = IO ()-type Opts = (MaybeShowVer, [String])+type Printer = X.Text -> IO ()+type PosArg = String -posArg :: String -> Opts -> Opts-posArg s (a, ss) = (a, s:ss)+type Arg = S.S3 MaybeShowVer Printer PosArg -allOpts :: [MA.OptSpec (Opts -> Opts)]-allOpts = [ fmap (\a (_, ss) -> (a, ss))-            $ Ly.version PPB.version ]+allOpts :: [MA.OptSpec Arg]+allOpts =+  [ fmap S.S3a $ Ly.version PPB.version+  , fmap S.S3b $ Ly.output+  ]  main :: IO () main = do-  as <- MA.simpleWithHelp help MA.Intersperse allOpts posArg-  let opts = foldr ($) (return (), []) as-  fst opts-  l <- C.open . snd $ opts-  case R.ledger groupSpecs l of+  as <- MA.simpleWithHelp help MA.Intersperse allOpts (fmap return S.S3c)+  let (showVers, printers, posArgs) = S.partitionS3 as+  sequence_ showVers+  l <- C.open posArgs+  case mapM (R.item groupSpecs) (map C.stripMeta l) of     Nothing -> error "could not render final ledger."-    Just x -> TIO.putStr x+    Just x ->+      let txt = X.concat x+      in txt `seq` (Ly.processOutput printers txt)
penny-selloff.hs view
@@ -93,20 +93,19 @@  module Main (main) where +import Control.Arrow (first)+import Control.Applicative ((<$>), (<*>), pure) import qualified Control.Monad.Exception.Synchronous as Ex import Control.Monad (when) import qualified Control.Monad.Trans.State as St import Control.Monad.Trans.Class (lift)-import Data.Foldable (toList) import Data.List (find)-import Data.List.NonEmpty (NonEmpty((:|)))-import qualified Data.List.NonEmpty as NE import Data.Maybe (isJust, mapMaybe, catMaybes, fromMaybe) import Data.Text (pack) import qualified Data.Text as X import qualified Data.Text.IO as TIO import qualified Penny.Lincoln.Balance as Bal-import qualified Penny.Lincoln.Transaction.Unverified as U+import qualified Penny.Steel.Sums as S import qualified Penny.Cabin.Balance.Util as BU import Penny.Cabin.Options (ShowZeroBalances(..)) import qualified Penny.Copper as Cop@@ -116,7 +115,7 @@ import qualified Penny.Lincoln as L import qualified Data.Map as M import qualified System.Console.MultiArg as MA-import Text.Parsec as Parsec+import qualified Text.Parsec as Parsec import qualified Paths_penny_bin as PPB  groupingSpec :: CR.GroupSpecs@@ -128,7 +127,6 @@   = ParseFail MA.Error   | NoInputArgs   | ProceedsParseFailed Parsec.ParseError-  | LedgerParseError Cop.ErrorMsg   | NoSelloffAccount   | NotThreeSelloffSubAccounts   | BadSelloffBalance@@ -157,11 +155,12 @@ allOpts :: [MA.OptSpec (Opts -> Opts)] allOpts = [ fmap (\a (_, ss) -> (a, ss)) $ Ly.version PPB.version ] -data ParseResult = ParseResult ProceedsAcct Cop.Ledger+data ParseResult = ParseResult ProceedsAcct [Cop.LedgerItem]  parseCommandLine :: IO ParseResult parseCommandLine = do-  as <- MA.simpleWithHelp help MA.Intersperse allOpts posArg+  as <- MA.simpleWithHelp help MA.Intersperse allOpts+        (fmap return posArg)   let opts = foldr ($) (return (), []) as   fst opts   x:xs <- case snd opts of@@ -183,17 +182,14 @@   , "  --version  - show version and exit."   ] -calcBalances :: Cop.Ledger -> [(L.Account, L.Balance)]+calcBalances :: [Cop.LedgerItem] -> [(L.Account, L.Balance)] calcBalances =-  let toTxn i = case i of-        Cop.Transaction t -> Just t-        _ -> Nothing-  in BU.flatten-      . BU.balances (ShowZeroBalances False)-      . map (L.Box ())-      . concatMap L.postFam-      . mapMaybe toTxn-      . Cop.unLedger+  BU.flatten+  . BU.balances (ShowZeroBalances False)+  . map (\p -> ((), p))+  . concatMap L.transactionToPostings+  . mapMaybe (S.caseS4 Just (const Nothing) (const Nothing)+              (const Nothing))  newtype Group = Group { unGroup :: L.SubAccount }   deriving (Show, Eq)@@ -242,16 +238,14 @@         Bal.NonZero col -> Just (cy, col)       ps = mapMaybe toPair . M.toList $ m       findBal dc = Ex.fromMaybe BadSelloffBalance-                    . find ((== dc) . Bal.drCr . snd)+                    . find ((== dc) . Bal.colDrCr . snd)                     $ ps   (cyStock, (Bal.Column _ qtyStock)) <- findBal L.Debit   (cyCurr, (Bal.Column _ qtyCurr)) <- findBal L.Credit   let sellStock = SelloffStock-        (L.Amount qtyStock cyStock-          (Just L.CommodityOnLeft) (Just L.SpaceBetween))+        (L.Amount qtyStock cyStock)       sellCurr = SelloffCurrency-        (L.Amount qtyCurr cyCurr-          (Just L.CommodityOnLeft) (Just L.SpaceBetween))+        (L.Amount qtyCurr cyCurr)   return (sellStock, sellCurr)  @@ -317,7 +311,7 @@         Bal.NonZero col -> Just (cy, col)       ps = mapMaybe toPair . M.toList $ m       findBal dc = Ex.fromMaybe BadPurchaseBalance-                    . find ((== dc) . Bal.drCr . snd)+                    . find ((== dc) . Bal.colDrCr . snd)                     $ ps   (cyStock, (Bal.Column _ qtyStock)) <- findBal L.Credit   (cyCurr, (Bal.Column _ qtyCurr)) <- findBal L.Debit@@ -375,9 +369,9 @@           return (Just (p, br))          L.RightBiggerBy unsoldStockQty -> do-          let alloced = L.allocate pcq (sq :| [unsoldStockQty])+          let alloced = L.allocate pcq (sq, [unsoldStockQty])               basisSold = case alloced of-                x :| (_ : []) -> x+                (x, (_ : [])) -> x                 _ -> error "stRealizeBasis: error"           let css' = case mayCss of                 Nothing -> CostSharesSold basisSold@@ -441,14 +435,28 @@     Nothing -> return . NoChange $ ls     Just (qt, gl) -> do       nePurchs <- Ex.fromMaybe NoPurchaseInformation-                  . NE.nonEmpty $ ls-      let qtys = fmap (unPurchaseCurrencyQty . piCurrencyQty . fst)+                  . uncons $ ls+      let qtys = mapNE (unPurchaseCurrencyQty . piCurrencyQty . fst)                  nePurchs           alloced = L.allocate qt qtys       let mkCapChange (p, br) q = (p, br, CapitalChange q)-          r = toList $ NE.zipWith mkCapChange nePurchs alloced+          r = flattenNE $ zipNE mkCapChange nePurchs alloced       return $ WithCapitalChanges r gl +mapNE :: (a -> b) -> (a, [a]) -> (b, [b])+mapNE f (a, as) = (f a, map f as)++flattenNE :: (a, [a]) -> [a]+flattenNE (a, as) = a:as++uncons :: [a] -> Maybe (a, [a])+uncons as = case as of+  [] -> Nothing+  x:xs -> Just (x, xs)++zipNE :: (a -> b -> c) -> (a, [a]) -> (b, [b]) -> (c, [c])+zipNE f (a, as) (b, bs) = (f a b, zipWith f as bs)+ memo :: SaleDate -> L.Memo memo (SaleDate sd) =   let dTxt = CR.dateTime sd@@ -459,17 +467,21 @@ payee :: L.Payee payee = L.Payee . pack $ "Realize gain or loss" -topLine :: SaleDate -> U.TopLine-topLine sd = (U.emptyTopLine (unSaleDate sd))-             { U.tPayee = Just payee-             , U.tMemo = Just . memo $ sd+topLine :: SaleDate -> L.TopLineData+topLine sd =+  let core = (L.emptyTopLineCore (unSaleDate sd))+             { L.tPayee = Just payee+             , L.tMemo = Just . memo $ sd              }+  in L.TopLineData { L.tlCore = core+                   , L.tlFileMeta = Nothing+                   , L.tlGlobal = Nothing }  basisOffsets   :: SelloffInfo   -> PurchaseDate   -> BasisRealiztn-  -> (U.Posting, U.Posting)+  -> ((L.Entry, L.PostingData), (L.Entry, L.PostingData)) basisOffsets s pd p = (po enDr, po enCr)   where     ac = L.Account [basis, grp, dt]@@ -477,15 +489,23 @@     dt = dateToSubAcct . unPurchaseDate $ pd     enDr = L.Entry L.Debit            (L.Amount (unRealizedStockQty . brStockQty $ p)-              (L.commodity . unSelloffStock . siStock $ s)-              (Just L.CommodityOnLeft) (Just L.SpaceBetween))+              (L.commodity . unSelloffStock . siStock $ s))     enCr = L.Entry L.Credit            (L.Amount (unRealizedCurrencyQty . brCurrencyQty $ p)-              (L.commodity . unSelloffCurrency . siCurrency $ s)-              (Just L.CommodityOnLeft) (Just L.SpaceBetween))-    po en = (U.emptyPosting ac)-            { U.pEntry = Just en }+              (L.commodity . unSelloffCurrency . siCurrency $ s))+    po en = (en, emptyPostingData ac) +emptyPostingData :: L.Account -> L.PostingData+emptyPostingData a =+  let core = (L.emptyPostingCore a)+        { L.pSide = Just L.CommodityOnLeft+        , L.pSpaceBetween = Just L.SpaceBetween+        }+  in L.PostingData { L.pdCore = core+                   , L.pdFileMeta = Nothing+                   , L.pdGlobal = Nothing+                   }+ dateToSubAcct :: L.DateTime -> L.SubAccount dateToSubAcct = L.SubAccount . CR.dateTime @@ -519,25 +539,21 @@   -> SelloffCurrency   -> CapitalChange   -> L.Entry-capChangeEntry gl sc cc = L.Entry dc (L.Amount qt cy sd sb)+capChangeEntry gl sc cc = L.Entry dc (L.Amount qt cy)   where     dc = case gl of       Gain -> L.Credit       Loss -> L.Debit     cy = L.commodity . unSelloffCurrency $ sc     qt = unCapitalChange cc-    sd = Just L.CommodityOnLeft-    sb = Just L.SpaceBetween  capChangePstg   :: SelloffInfo   -> GainOrLoss   -> CapitalChange   -> PurchaseInfo-  -> U.Posting-capChangePstg si gl cc p =-  (U.emptyPosting ac)-  { U.pEntry = Just en }+  -> (L.Entry, L.PostingData)+capChangePstg si gl cc p = (en, emptyPostingData ac)   where     ac = capChangeAcct gl si p     en = capChangeEntry gl (siCurrency si) cc@@ -547,10 +563,10 @@  proceedsPstgs   :: SelloffInfo-  -> (U.Posting, U.Posting)+  -> ((L.Entry, L.PostingData), (L.Entry, L.PostingData)) proceedsPstgs si = (po dr, po cr)   where-    po en = (U.emptyPosting ac) { U.pEntry = Just en }+    po en = (en, emptyPostingData ac)     ac = L.Account [proceeds, gr, dt]     gr = unGroup . siGroup $ si     dt = dateToSubAcct . unSaleDate . siSaleDate $ si@@ -562,10 +578,11 @@   :: SelloffInfo   -> WithCapitalChanges   -> L.Transaction-mkTxn si wcc = Ex.resolve err exTxn+mkTxn si wcc = fromMaybe err exTxn   where-    err = const $ error "mkTxn: making transaction failed"-    exTxn = L.transaction $ L.Family tl p1 p2 ps+    err = error "mkTxn: making transaction failed"+    exTxn = (\topl es -> L.Transaction (topl, es))+            <$> pure tl <*> L.ents entInputs     tl = topLine . siSaleDate $ si     (p1, p2) = proceedsPstgs si     ps = case wcc of@@ -580,10 +597,11 @@             where               (b1, b2) = basisOffsets si (piDate p) br               c = capChangePstg si gl cc p+    entInputs = map (first Just) (p1:p2:ps)  makeOutput   :: ProceedsAcct-  -> Cop.Ledger+  -> [Cop.LedgerItem]   -> Err X.Text makeOutput pa ldgr = do   let bals = calcBalances ldgr@@ -597,6 +615,8 @@     . (`X.snoc` '\n')     . fromMaybe (error "makeOutput: transaction did not render")     . CR.transaction groupingSpec+    . (\t -> let (tl, es) = L.unTransaction t+             in (L.tlCore tl, fmap L.pdCore es))     . mkTxn si     $ wcc