diff --git a/doc/ledger-grammar.org b/doc/ledger-grammar.org
--- a/doc/ledger-grammar.org
+++ b/doc/ledger-grammar.org
@@ -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
 
diff --git a/doc/penny-fit-sample.hs b/doc/penny-fit-sample.hs
new file mode 100644
--- /dev/null
+++ b/doc/penny-fit-sample.hs
@@ -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
diff --git a/doc/penny-ofx.ini b/doc/penny-ofx.ini
new file mode 100644
--- /dev/null
+++ b/doc/penny-ofx.ini
@@ -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".
diff --git a/man/penny-fit.1 b/man/penny-fit.1
deleted file mode 100644
--- a/man/penny-fit.1
+++ /dev/null
@@ -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)
diff --git a/man/penny-fit.7 b/man/penny-fit.7
new file mode 100644
--- /dev/null
+++ b/man/penny-fit.7
@@ -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)
diff --git a/man/penny-reconcile.1 b/man/penny-reconcile.1
--- a/man/penny-reconcile.1
+++ b/man/penny-reconcile.1
@@ -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)
diff --git a/man/penny-reprint.1 b/man/penny-reprint.1
--- a/man/penny-reprint.1
+++ b/man/penny-reprint.1
@@ -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)
diff --git a/man/penny-suite.7 b/man/penny-suite.7
--- a/man/penny-suite.7
+++ b/man/penny-suite.7
@@ -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.
diff --git a/penny-bin.cabal b/penny-bin.cabal
--- a/penny-bin.cabal
+++ b/penny-bin.cabal
@@ -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
+
diff --git a/penny-diff.hs b/penny-diff.hs
--- a/penny-diff.hs
+++ b/penny-diff.hs
@@ -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
diff --git a/penny-reconcile.hs b/penny-reconcile.hs
--- a/penny-reconcile.hs
+++ b/penny-reconcile.hs
@@ -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)
 
diff --git a/penny-reprint.hs b/penny-reprint.hs
--- a/penny-reprint.hs
+++ b/penny-reprint.hs
@@ -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)
diff --git a/penny-selloff.hs b/penny-selloff.hs
--- a/penny-selloff.hs
+++ b/penny-selloff.hs
@@ -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
 
