initial import into Darcs
This commit is contained in:
parent
31dafc3c16
commit
9ca1d24d30
20 changed files with 1502 additions and 1302 deletions
23
Makefile
23
Makefile
|
|
@ -1,10 +1,12 @@
|
||||||
CODE = amount.cc ledger.cc parse.cc reports.cc
|
CODE = amount.cc ledger.cc parse.cc reports.cc
|
||||||
OBJS = $(patsubst %.cc,%.o,$(CODE))
|
OBJS = $(patsubst %.cc,%.o,$(CODE))
|
||||||
|
CXX = cc
|
||||||
|
#CXX = g++
|
||||||
CFLAGS = #-Wall -ansi -pedantic
|
CFLAGS = #-Wall -ansi -pedantic
|
||||||
DFLAGS = -O3 -fomit-frame-pointer
|
#DFLAGS = -O3 -fomit-frame-pointer
|
||||||
#DFLAGS = -g -DDEBUG=1
|
DFLAGS = -g -DDEBUG=1
|
||||||
INCS = -I/usr/local/include
|
INCS = -I/sw/include -I/usr/include/gcc/darwin/3.3/c++ -I/usr/include/gcc/darwin/3.3/c++/ppc-darwin
|
||||||
LIBS = -L/usr/local/lib -lgmpxx -lgmp -lpcre
|
LIBS = -L/sw/lib -lgmpxx -lgmp -lpcre
|
||||||
|
|
||||||
ifdef GNUCASH
|
ifdef GNUCASH
|
||||||
CODE := $(CODE) gnucash.cc
|
CODE := $(CODE) gnucash.cc
|
||||||
|
|
@ -15,17 +17,24 @@ endif
|
||||||
|
|
||||||
all: make.deps ledger ledger.info
|
all: make.deps ledger ledger.info
|
||||||
|
|
||||||
|
install: all
|
||||||
|
strip ledger
|
||||||
|
cp ledger $(HOME)/bin
|
||||||
|
|
||||||
ledger: $(OBJS)
|
ledger: $(OBJS)
|
||||||
g++ $(CFLAGS) $(INCS) $(DFLAGS) -o $@ $(OBJS) $(LIBS)
|
$(CXX) $(CFLAGS) $(INCS) $(DFLAGS) -o $@ $(OBJS) $(LIBS)
|
||||||
|
|
||||||
ledger.info: ledger.texi
|
ledger.info: ledger.texi
|
||||||
makeinfo $<
|
makeinfo $<
|
||||||
|
|
||||||
%.o: %.cc
|
%.o: %.cc
|
||||||
g++ $(CFLAGS) $(INCS) $(DFLAGS) -c -o $@ $<
|
$(CXX) $(CFLAGS) $(INCS) $(DFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f ledger ledger.info *.o *~ .\#* *.elc
|
rm -f ledger *.o *.elc *~ .\#*
|
||||||
|
|
||||||
|
distclean fullclean: clean
|
||||||
|
rm -f ledger.info README.html README.pdf *.elc make.deps
|
||||||
|
|
||||||
rebuild: clean deps all
|
rebuild: clean deps all
|
||||||
|
|
||||||
|
|
|
||||||
12
NEWS
Normal file
12
NEWS
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
1.6
|
||||||
|
|
||||||
|
Can now parse timeclock files. These are simple timelogs that track
|
||||||
|
in/out events, which can be maintained using my timeclock tool. By
|
||||||
|
allowing ledger to parse these, it means that reporting can be done
|
||||||
|
on them in the same way as a ledger file (the commodities is "h",
|
||||||
|
for hours); and it means that doing things like tracking billable
|
||||||
|
hours for clients, and invoicing those clients to transfer those
|
||||||
|
hours into a dollar value via a receivable account, is now trivial.
|
||||||
|
See the docs for more on how to do this.
|
||||||
|
|
||||||
|
Began keeping NEWS file. :)
|
||||||
690
README
690
README
|
|
@ -1,19 +1,111 @@
|
||||||
|
#comment -*-muse-*-
|
||||||
#title Ledger: Command-Line Accounting
|
#title Ledger: Command-Line Accounting
|
||||||
|
|
||||||
<contents>
|
<contents>
|
||||||
|
|
||||||
* Building
|
* Introduction
|
||||||
|
|
||||||
Welcome to ledger, a command-line accounting program that uses a
|
Ledger is an accounting tool with the moxie to exist. It provides no
|
||||||
text-based ledger file, yet provides full double-entry accounting, use
|
bells or whistles, and returns the user to the days before user
|
||||||
of commodities, unlimited accounts, etc.
|
interfaces were even a twinkling in their father's CRT.
|
||||||
|
|
||||||
|
What it does do is to offer a double-entry accounting ledger with all
|
||||||
|
the flexibility and muscle of its modern day cousins, without any of
|
||||||
|
the fat. Think of it as the Great Bran Muffin of accounting tools.
|
||||||
|
|
||||||
|
To use it, you need to start keeping a ledger. This is the basis of
|
||||||
|
all accounting, and if you haven't started yet, now is the time to
|
||||||
|
learn. The little booklet that comes with your checkbook is a ledger,
|
||||||
|
so we'll describe double-entry accounting in terms of that.
|
||||||
|
|
||||||
|
A checkbook ledger records debits (subtractions, or withdrawals) and
|
||||||
|
credits (additions, or deposits) with reference to a single account:
|
||||||
|
the checking account. Where the money comes from, and where it goes
|
||||||
|
to, are described in the payee field, where you write the person or
|
||||||
|
company's name. The ultimate aim of keeping a checkbook ledger is to
|
||||||
|
know how much money is available to spend. That's really the aim of
|
||||||
|
all ledgers.
|
||||||
|
|
||||||
|
What computers add is the ability to walk through these transactions,
|
||||||
|
and tell you things about your spending habits; to let you devise
|
||||||
|
budgets and get control over your spending; to squirrel away money
|
||||||
|
into virtual savings account without having to physically move money
|
||||||
|
around; etc. As you keep your ledger, you are recording information
|
||||||
|
about your life and habits, and sometimes that information can start
|
||||||
|
telling you things you aren't aware of. Such is the aim of all good
|
||||||
|
accounting tools.
|
||||||
|
|
||||||
|
The next step up from a checkbook ledger, is a ledger that keeps track
|
||||||
|
of all your accounts, not just checking. In such a ledger, you record
|
||||||
|
not only who gets paid -- in the case of a debit -- but where the
|
||||||
|
money came from. In a checkbook ledger, its assumed that all the
|
||||||
|
money comes from your checking account. But in a general ledger, you
|
||||||
|
write transaction two-lines: The source account and target account.
|
||||||
|
*There must always be a debit from at least one account for every
|
||||||
|
credit made to another account*. This is what is meant by
|
||||||
|
"double-entry" accounting: the ledger must always balance to zero,
|
||||||
|
with an equal number of debits and credits.
|
||||||
|
|
||||||
|
For example, let's say you have a checking account and a brokerage
|
||||||
|
account, and you can write checks from both of them. Rather than keep
|
||||||
|
two checkbooks, you decide to use one ledger for both. In this
|
||||||
|
general ledger you need to record a payment to Pacific Bell for your
|
||||||
|
monthly phone bill. The cost is $23.00, let's say, and you want to
|
||||||
|
pay it from your checking account. In the general ledger you need to
|
||||||
|
say where the money came from, in addition to where it's going to.
|
||||||
|
The entry might look like this:
|
||||||
|
|
||||||
|
<example>
|
||||||
|
9/29 BAL Pacific Bell $200.00 $200.00
|
||||||
|
Equity:Opening Balances $-200.00
|
||||||
|
9/29 BAL Checking $100.00 $100.00
|
||||||
|
Equity:Opening Balances $-100.00
|
||||||
|
9/29 100 Pacific Bell $23.00 $223.00
|
||||||
|
Checking $-23.00 $77.00
|
||||||
|
</example>
|
||||||
|
|
||||||
|
The first line shows a credit (or payment) to Pacific Bell for $23.00.
|
||||||
|
Because there is no "balance" in a general ledger -- it's always zero
|
||||||
|
-- we write in the total balance of all payments to "Pacific Bell",
|
||||||
|
which now is $223.00 (previously the balance was $200.00). This is
|
||||||
|
done by looking at the last entry for "Pacific Bell" in the ledger,
|
||||||
|
adding $23.00 to that amount, and writing the total in the balance
|
||||||
|
column. And the money came from "Checking" -- a debit (or withdrawal)
|
||||||
|
of $23.00 -- which leaves the ending balance in "Checking" at $77.00.
|
||||||
|
This is a very manual procedure; but that's where computers come in...
|
||||||
|
|
||||||
|
The transaction must balance to $0: $23 went to Pacific Bell, $23 came
|
||||||
|
from Checking. There is nothing left over to be accounted for, since
|
||||||
|
the money has simply moved from one account to another. This is the
|
||||||
|
basis of double-entry accounting: that money never pops in or out of
|
||||||
|
existence; it is always a transaction from one account to another.
|
||||||
|
|
||||||
|
Keeping a general ledger is the same as keeping two separate ledgers:
|
||||||
|
One for Pacific Bell and one for Checking. In that case, each time a
|
||||||
|
credit is written into one, you write a corresponding debit into the
|
||||||
|
other. This makes it easier to write in a "running balance", since
|
||||||
|
you don't have to look back at the last time the account was
|
||||||
|
referenced -- but it also means having a lot of ledger books, if you
|
||||||
|
deal with multiple accounts.
|
||||||
|
|
||||||
|
Enter the beauty of computerized accounting. The purpose of the
|
||||||
|
Ledger program is to make general ledger accounting simple, by keeping
|
||||||
|
track of the balances for you. Your only job is to enter the
|
||||||
|
credit/debit transactions. If a transaction does not balance, Ledger
|
||||||
|
will display an error and indicate which transaction is wrong.[1]
|
||||||
|
|
||||||
|
In summary, there are two aspects of Ledger use: Updating the ledger
|
||||||
|
data file, and using the Ledger tool to view the summarized result of
|
||||||
|
your entries.
|
||||||
|
|
||||||
|
* Building the program
|
||||||
|
|
||||||
Ledger is written in ANSI C++, and should compile on any platform. It
|
Ledger is written in ANSI C++, and should compile on any platform. It
|
||||||
only depends on the GNU multiprecision integer library (gmp, or
|
depends only on the GNU multiprecision integer library (libgmp), and
|
||||||
libgmp), and the Perl regular expression library (pcre, or libpcre).
|
the Perl regular expression library (libpcre). It was developed using
|
||||||
Also, this project was developed using GNU make and gcc 3.3.
|
GNU make and gcc 3.3.
|
||||||
|
|
||||||
To build and install, once you have these libraries on your system,
|
To build and install once you have these libraries on your system,
|
||||||
enter these commands:
|
enter these commands:
|
||||||
|
|
||||||
<example>
|
<example>
|
||||||
|
|
@ -21,110 +113,10 @@ make
|
||||||
cp ledger /usr/local/bin
|
cp ledger /usr/local/bin
|
||||||
</example>
|
</example>
|
||||||
|
|
||||||
* Introduction
|
Note that when building GNUmp, make sure to pass the =--enable-cxx=
|
||||||
|
flag to configure, otherwise it will not build **libgmpxx.a**. And in
|
||||||
Ledger is an accounting tool with the moxie to exist. It provides not
|
case it is not already on your system, **xmlparse.h** is part of the
|
||||||
one bell or whistle for the money, and returns the user to the days
|
libxmltok package, and not expat.
|
||||||
before user interfaces were even a twinkle in their father's CRT.
|
|
||||||
|
|
||||||
What it does do is provide a double-entry accounting ledger with all
|
|
||||||
of the flexibility and muscle of its modern day cousins -- without any
|
|
||||||
of the fat. Think of it as the bran muffin of accounting tools.
|
|
||||||
|
|
||||||
To begin with, you need to start keeping a ledger. This is the basis
|
|
||||||
of all accounting, and if you don't know how to do it, now is the time
|
|
||||||
to learn. The little booklet that comes with your checkbook is a
|
|
||||||
ledger, so we'll describe double-entry accounting in terms of that.
|
|
||||||
|
|
||||||
A checkbook ledger records debits (subtractions, or withdrawals) and
|
|
||||||
credits (additions, or deposits) with reference to a single account:
|
|
||||||
your checking account. Where the money comes from, and where it goes
|
|
||||||
to, are simply described in the memo field where you write the person
|
|
||||||
or the company's name. The ultimate aim of keeping a checkbook ledger
|
|
||||||
is so you know how much money is available to spend at all times.
|
|
||||||
That is really the aim of all ledgers.
|
|
||||||
|
|
||||||
What computers add is the ability to walk through all of those
|
|
||||||
transactions and tell you things about your spending habits; let you
|
|
||||||
devise budgets to get control over your spending; squirrel away money
|
|
||||||
into virtual savings account without having to physically move the
|
|
||||||
money around; etc. As you keep your checkbook ledger, you are
|
|
||||||
recording a lot of information about your life and your habits, and
|
|
||||||
sometimes that information can tell you things you aren't even aware
|
|
||||||
of. That is the aim of all good accounting tools.
|
|
||||||
|
|
||||||
The next step up from a checkbook ledger is a ledger that covers all
|
|
||||||
of your accounts, not just your checking account. In this ledger, you
|
|
||||||
write not only who the money goes to -- in the case of a debit -- but
|
|
||||||
where the money is coming from. In the checkbook ledger, its assumed
|
|
||||||
that all of the money is coming from your checking account. But in a
|
|
||||||
general ledger, you have to write two-lines: The source and target.
|
|
||||||
There must always be a debit from some account for any credit made to
|
|
||||||
anyone else. This is what is meant by "double-entry" accounting.
|
|
||||||
|
|
||||||
For example, let's say you have a checking account and a brokerage
|
|
||||||
account, and that you can write checks from both of them. Rather than
|
|
||||||
keeping two checkbooks, you decide to use one ledger for both. Once
|
|
||||||
you get the hang of this, you'll be ready to use one ledger for all of
|
|
||||||
your accounting needs, which gets you to the point of this
|
|
||||||
introduction.
|
|
||||||
|
|
||||||
So in your general ledger, you need to pay Pacific Bell Telephone for
|
|
||||||
your monthly phone bill. The cost is $23.00. In a checkbook ledger,
|
|
||||||
you would write out a line that credits your account with Pacific Bell
|
|
||||||
by $23 as follows:
|
|
||||||
|
|
||||||
<example>
|
|
||||||
9/29 100 Pacific Bell $23.00 $77.00
|
|
||||||
</example>
|
|
||||||
|
|
||||||
Very simple: You've written check #100 for $23 to Pacific Bell, which
|
|
||||||
leaves your balance in checking at $77.
|
|
||||||
|
|
||||||
But in a general ledger, you need to say where the money is coming
|
|
||||||
from. A general ledger entry would look like this:
|
|
||||||
|
|
||||||
<example>
|
|
||||||
9/29 100 Pacific Bell $23.00 $223.00
|
|
||||||
Checking $-23.00 $77.00
|
|
||||||
</example>
|
|
||||||
|
|
||||||
What does all of this mean? The first line shows a credit (or
|
|
||||||
payment) to Pacific Bell to the tune of $23.00. Then, because there
|
|
||||||
is no one "balance" in a general ledger, we've written in the total
|
|
||||||
balance of your payments to the account "Pacific Bell". This was done
|
|
||||||
by looking at the last entry for "Pacific Bell" in the general ledger,
|
|
||||||
adding $23.00 to that amount, and writing in the total in the balance
|
|
||||||
column.
|
|
||||||
|
|
||||||
Secondly, the money is coming from your "Checking" account, which
|
|
||||||
means a debit (or withdrawal) of $23.00, which will leave the ending
|
|
||||||
balance in your "Checking" account at $77.00.
|
|
||||||
|
|
||||||
The transaction itself must balance to $0: $23 goes to Pacific Bell,
|
|
||||||
$23 comes from Checking: there is nothing left over to be accounted
|
|
||||||
for. The money has in fact moved from one account to another. This
|
|
||||||
is basis of double-entry accounting: That money never pops out of
|
|
||||||
existence, it is always described as a transaction between accounts --
|
|
||||||
as a flow from one place to another.
|
|
||||||
|
|
||||||
Keeping a general ledger is the same as keeping two separate ledgers:
|
|
||||||
One for Pacific Bell and one for Checking. In that case, each time
|
|
||||||
you write a credit into one, you write a corresponding debit into the
|
|
||||||
other. This makes it much easier to write in the running balance,
|
|
||||||
since you don't have to go looking back for the last time an account
|
|
||||||
was referenced, but it also means having a lot of ledger books if you
|
|
||||||
deal with multiple accounts.
|
|
||||||
|
|
||||||
Enter the beauty of a computerized accounting tool. The purpose of
|
|
||||||
Ledger is to make general ledger accounting simple by keeping track of
|
|
||||||
the balances for you. Your only job is to enter credit/debit pairs
|
|
||||||
and make sure they balance. If a transaction does not balance, Ledger
|
|
||||||
will display an error and ignore the transaction.[1]
|
|
||||||
|
|
||||||
Your usage of Ledger will have two parts: Keeping the ledger, and
|
|
||||||
using the Ledger tool to provide you with information summaries
|
|
||||||
derived from your ledger's entries.
|
|
||||||
|
|
||||||
* Keeping a ledger
|
* Keeping a ledger
|
||||||
|
|
||||||
|
|
@ -302,7 +294,7 @@ FEQTX=$32
|
||||||
Specify the prices file using the =-p= option:
|
Specify the prices file using the =-p= option:
|
||||||
|
|
||||||
<example>
|
<example>
|
||||||
/home/johnw $ ledger -p prices.db balance brokerage
|
ledger -p prices.db balance brokerage
|
||||||
</example>
|
</example>
|
||||||
|
|
||||||
Now the balance for your brokerage account will be given in US
|
Now the balance for your brokerage account will be given in US
|
||||||
|
|
@ -319,7 +311,7 @@ wanted to know many ounces of gold that would buy. If gold is
|
||||||
currently $357 per ounce, then each dollar is worth 1/357 AU:
|
currently $357 per ounce, then each dollar is worth 1/357 AU:
|
||||||
|
|
||||||
<example>
|
<example>
|
||||||
/home/johnw $ ledger -p "$=0.00280112 AU" balance checking
|
ledger -p "$=0.00280112 AU" balance checking
|
||||||
</example>
|
</example>
|
||||||
|
|
||||||
<example>
|
<example>
|
||||||
|
|
@ -392,7 +384,7 @@ Where will money come from? The answer: your equity.
|
||||||
<example>
|
<example>
|
||||||
10/2 Opening Balance
|
10/2 Opening Balance
|
||||||
Assets:Checking $100.00
|
Assets:Checking $100.00
|
||||||
Equity:Opening Balances $-100.00
|
Equity:Opening Balances
|
||||||
</example>
|
</example>
|
||||||
|
|
||||||
But what is equity? You may have heard of equity when people talked
|
But what is equity? You may have heard of equity when people talked
|
||||||
|
|
@ -417,64 +409,35 @@ Clear as mud? Keep thinking about it. Until you figure it out, put
|
||||||
"-- -Equity" at the end of your balance command, to remove the
|
"-- -Equity" at the end of your balance command, to remove the
|
||||||
confusing figure from the totals.
|
confusing figure from the totals.
|
||||||
|
|
||||||
* Using the Ledger Tool
|
** Dealing with cash
|
||||||
|
|
||||||
Now that you have an orderly and well-organized general ledger, it's
|
Something that stops many people from keeping a ledger at all is the
|
||||||
time to start generating some orderly and well-organized reports.
|
insanity of tracking cash expenses. They rarely generate a receipt,
|
||||||
This is where the Ledger tool comes in. With it, you can balance your
|
and there are often a lot of small transactions, rather than a few
|
||||||
checkbook, see where your money is going, tell whether you've made a
|
large ones, as with checks.
|
||||||
profit this year, and even compute the present day value of your
|
|
||||||
retirement accounts. And all with the simplest of interfaces: the
|
|
||||||
command-line.
|
|
||||||
|
|
||||||
The most often used command will be the "balance" command:
|
The answer is: don't bother. Move your spending to a debit card, but
|
||||||
|
in general ignore cash. Once you withdraw it from the ATM, mark it as
|
||||||
|
already spent to an "Expenses:Cash" category:
|
||||||
|
|
||||||
<example>
|
<example>
|
||||||
/home/johnw $ export LEDGER=/home/johnw/doc/ledger.dat
|
2004/03/15 ATM
|
||||||
/home/johnw $ ledger balance
|
Expenses:Cash $100.00
|
||||||
|
Assets:Checking
|
||||||
</example>
|
</example>
|
||||||
|
|
||||||
Here I've set my Ledger environment variable to point to where my
|
If at some point you make a large cash expense that you want to track,
|
||||||
ledger file is hiding. Thereafter, I needn't specify it again.
|
just "move" the amount of the expense from "Expenses:Cash" into the
|
||||||
|
target account:
|
||||||
The balance command prints out the summarized balances of all my
|
|
||||||
top-level accounts, excluding sub-accounts. In order to see the
|
|
||||||
balances for a specific account, just specify a regular expression
|
|
||||||
after the balance command:
|
|
||||||
|
|
||||||
<example>
|
<example>
|
||||||
/home/johnw $ ledger balance expenses:food
|
2004/03/20 Somebody
|
||||||
|
Expenses:Food $65.00
|
||||||
|
Expenses:Cash
|
||||||
</example>
|
</example>
|
||||||
|
|
||||||
This will show all the money that's been spent on food, since the
|
This way, you can still track large cash expenses, while ignoring all
|
||||||
beginning of the ledger. For food spending just this month
|
of the smaller ones.
|
||||||
(September), use:
|
|
||||||
|
|
||||||
<example>
|
|
||||||
/home/johnw $ ledger -d sep balance expenses:food
|
|
||||||
</example>
|
|
||||||
|
|
||||||
Or maybe I want to see all of my assets, in which case the -s (show
|
|
||||||
sub-accounts) option comes in handy:
|
|
||||||
|
|
||||||
<example>
|
|
||||||
/home/johnw $ ledger balance -s
|
|
||||||
</example>
|
|
||||||
|
|
||||||
To exclude a particular account, use a regular expression with a
|
|
||||||
leading minus sign. The following will show all expenses, but without
|
|
||||||
food spending:
|
|
||||||
|
|
||||||
<example>
|
|
||||||
/home/johnw $ ledger balance expenses -food
|
|
||||||
</example>
|
|
||||||
|
|
||||||
If you want to show all accounts but for one account, remember to use
|
|
||||||
"--" to separate the exclusion pattern from the options list:
|
|
||||||
|
|
||||||
<example>
|
|
||||||
/home/johnw $ ledger balance -- -equity
|
|
||||||
</example>
|
|
||||||
|
|
||||||
** Virtual transactions
|
** Virtual transactions
|
||||||
|
|
||||||
|
|
@ -516,60 +479,188 @@ When balances are displayed, virtual transactions will be factored in.
|
||||||
To view balances without any virtual balances factored in, using the
|
To view balances without any virtual balances factored in, using the
|
||||||
"-R" flag, for "Reality".
|
"-R" flag, for "Reality".
|
||||||
|
|
||||||
*** Saving for a Special Occasion
|
Write about: Saving for a Special Occasion; Keeping a Budget; Tracking
|
||||||
|
Allocated Funds.
|
||||||
*** Keeping a Budget
|
|
||||||
|
|
||||||
*** Tracking Allocated Funds
|
|
||||||
|
|
||||||
** Automated transactions
|
** Automated transactions
|
||||||
|
|
||||||
*** Computing Bahá'í Huqúqu'lláh
|
As a Bahá'í, I need to compute Huqúqu'lláh whenever I acquire assets.
|
||||||
|
The exact details of this are a bit complex, so if you have further
|
||||||
As a Bahá'í, I need to compute Huqúqu'lláh on some of my assets. The
|
interest, please consult the Web.
|
||||||
exact details of this matter are rather complex, so if you have any
|
|
||||||
interest, I encourage you to do research on the Web.
|
|
||||||
|
|
||||||
For any fellow Bahá'ís out there who want to track Huqúqu'lláh, the
|
For any fellow Bahá'ís out there who want to track Huqúqu'lláh, the
|
||||||
Ledger tool makes this extraordinarily easy. Just too easy, in fact.
|
Ledger tool makes this extremely easy. Just set up the following
|
||||||
Here's all you have to do: If an expense is exempt from Huqúqu'lláh --
|
automated transaction at the top of your ledger file:
|
||||||
a "necessary" expense -- put an exclamation mark before the account
|
|
||||||
name:
|
|
||||||
|
|
||||||
<example>
|
<example>
|
||||||
2002.12.31 Rent
|
; These entries will compute Huqúqu'lláh based on the
|
||||||
!Expenses:Rent $450.00
|
; contents of the ledger.
|
||||||
Assets:Checking $-450.00
|
|
||||||
|
= ^Income:
|
||||||
|
= ^Expenses:Rent$
|
||||||
|
= ^Expenses:Furnishings
|
||||||
|
= ^Expenses:Business
|
||||||
|
= ^Expenses:Taxes
|
||||||
|
= ^Expenses:Insurance
|
||||||
|
(Liabilities:Huqúqu'lláh) 0.19
|
||||||
</example>
|
</example>
|
||||||
|
|
||||||
Even easier than that, simply put a list of regular expressions that
|
This automated transaction works by looking at each transaction
|
||||||
match the categories you consider exempt in a file called =.huquq=,
|
appearing afterward in the ledger file. If any match the account
|
||||||
and the special marking will be done for you. Here is the file I use:
|
regexps, occurring after the equal signs above, 19% of the value of
|
||||||
|
that transaction is applied to the "Liabilities:Huqúqu'lláh" account.
|
||||||
|
So if $1000 is earned through Income:Salary, which is seen as a debit
|
||||||
|
from Income, a debit of $190 is applied to "Liabilities:Huqúqu'lláh";
|
||||||
|
if $1000 is spent on Rent -- seen as a credit to the Expense account
|
||||||
|
-- a credit of $190 is applied to Huqúqu'lláh. The ultimate balance
|
||||||
|
of Huqúqu'lláh reflects how much must be paid to that account in order
|
||||||
|
to balance it to zero.
|
||||||
|
|
||||||
|
When you're ready to pay, just write a check directly to the account
|
||||||
|
"Liabilities:Huqúqu'lláh":
|
||||||
|
|
||||||
<example>
|
<example>
|
||||||
^Income:
|
2003/01/01 (101) Baha'i Huqúqu'lláh Trust
|
||||||
^Retirement:
|
Liabilities:Huqúqu'lláh $1,000.00
|
||||||
^Expenses:Rent
|
Assets:Checking
|
||||||
^Expenses:Taxes
|
|
||||||
</example>
|
|
||||||
|
|
||||||
When you're ready to pay Huqúqu'lláh, just write the check to the
|
|
||||||
account "Huququ'llah" (no accents, one apostrophe):
|
|
||||||
|
|
||||||
<example>
|
|
||||||
2003.01.20 * (101) Baha'i Huququ'llah Trust
|
|
||||||
Huququ'llah $1,000.00
|
|
||||||
Assets:Checking $-1,000.00
|
|
||||||
</example>
|
</example>
|
||||||
|
|
||||||
That's it. To see how much Huqúq is currently owed based on your
|
That's it. To see how much Huqúq is currently owed based on your
|
||||||
ledger data, type:
|
ledger entries, use:
|
||||||
|
|
||||||
<example>
|
<example>
|
||||||
/home/johnw $ ledger balance ^huquq
|
ledger balance Liabilities:Huqúq
|
||||||
</example>
|
</example>
|
||||||
|
|
||||||
* An easy way to add new entries
|
* Running Ledger
|
||||||
|
|
||||||
|
Now that you have an orderly and well-organized general ledger, it's
|
||||||
|
time to start generating some orderly and well-organized reports.
|
||||||
|
This is where the Ledger tool comes in. With it, you can balance your
|
||||||
|
checkbook, see where your money is going, tell whether you've made a
|
||||||
|
profit this year, and even compute the present day value of your
|
||||||
|
retirement accounts. And all with the simplest of interfaces: the
|
||||||
|
command-line.
|
||||||
|
|
||||||
|
The most often used command will be the "balance" command:
|
||||||
|
|
||||||
|
<example>
|
||||||
|
export LEDGER=/home/johnw/doc/ledger.dat
|
||||||
|
ledger balance
|
||||||
|
</example>
|
||||||
|
|
||||||
|
Here I've set my Ledger environment variable to point to where my
|
||||||
|
ledger file is hiding. Thereafter, I needn't specify it again.
|
||||||
|
|
||||||
|
The balance command prints out the summarized balances of all my
|
||||||
|
top-level accounts, excluding sub-accounts. In order to see the
|
||||||
|
balances for a specific account, just specify a regular expression
|
||||||
|
after the balance command:
|
||||||
|
|
||||||
|
<example>
|
||||||
|
ledger balance expenses:food
|
||||||
|
</example>
|
||||||
|
|
||||||
|
This will show all the money that's been spent on food, since the
|
||||||
|
beginning of the ledger. For food spending just this month
|
||||||
|
(September), use:
|
||||||
|
|
||||||
|
<example>
|
||||||
|
ledger -d sep balance expenses:food
|
||||||
|
</example>
|
||||||
|
|
||||||
|
Or maybe I want to see all of my assets, in which case the -s (show
|
||||||
|
sub-accounts) option comes in handy:
|
||||||
|
|
||||||
|
<example>
|
||||||
|
ledger balance -s
|
||||||
|
</example>
|
||||||
|
|
||||||
|
To exclude a particular account, use a regular expression with a
|
||||||
|
leading minus sign. The following will show all expenses, but without
|
||||||
|
food spending:
|
||||||
|
|
||||||
|
<example>
|
||||||
|
ledger balance expenses -food
|
||||||
|
</example>
|
||||||
|
|
||||||
|
If you want to show all accounts but for one account, remember to use
|
||||||
|
"--" to separate the exclusion pattern from the options list:
|
||||||
|
|
||||||
|
<example>
|
||||||
|
ledger balance -- -equity
|
||||||
|
</example>
|
||||||
|
|
||||||
|
** Command summary
|
||||||
|
|
||||||
|
*** balance
|
||||||
|
|
||||||
|
The "balance" command reports the current balance of any account.
|
||||||
|
This command accepts a list of optional regexps, which will confine
|
||||||
|
the balance report to only matching accounts. By default, the
|
||||||
|
balances for all accounts will be printed. If an account contains
|
||||||
|
multiple types of commodities, each commodity's total is separately
|
||||||
|
reported.
|
||||||
|
|
||||||
|
*** register
|
||||||
|
|
||||||
|
The "register" command displays all the transactions occurring in a
|
||||||
|
single account, line by line. The account regexp must be specified as
|
||||||
|
the only argument to this command. If any regexps occur after the
|
||||||
|
required account name, the register will contain only those
|
||||||
|
transactions that match. Very useful for hunting down a particular
|
||||||
|
transaction.
|
||||||
|
|
||||||
|
The output from "register" is very close to what a typical checkbook,
|
||||||
|
or single account ledger, would look like. It also shows a running
|
||||||
|
balance. The final running balance of any register should always be
|
||||||
|
the same as the current balance of that account.
|
||||||
|
|
||||||
|
*** print
|
||||||
|
|
||||||
|
The "print" command prints out ledger entries just as they appear in
|
||||||
|
the original ledger. They will be properly formatted, and output in
|
||||||
|
the most economic form possible. The "print" command also takes a
|
||||||
|
list of optional regexps, which will cause only those transactions
|
||||||
|
which match in some way to be printed.
|
||||||
|
|
||||||
|
The "print" command is a handy way to clean up a ledger file whose
|
||||||
|
formatting has gotten out of hand.
|
||||||
|
|
||||||
|
*** equity
|
||||||
|
|
||||||
|
Equity transactions are used to establish the starting value of an
|
||||||
|
account. You might think of equity as the "ether" from which initial
|
||||||
|
balances appear.
|
||||||
|
|
||||||
|
The "equity" command makes it easy to archiving past years, and then
|
||||||
|
remove them without changing any current balances. For example, if
|
||||||
|
it's now 2004 and we want to archive all of 2003's transactions to
|
||||||
|
another file, write:
|
||||||
|
|
||||||
|
<example>
|
||||||
|
export LEDGER=ledger.dat
|
||||||
|
ledger -e 2004/1/1 print > ledger-2003.dat
|
||||||
|
ledger -e 2004/1/1 equity > /tmp/balances
|
||||||
|
ledger -b 2004/1/1 print > /tmp/current
|
||||||
|
cat /tmp/balances /tmp/current > ledger.dat
|
||||||
|
rm /tmp/balances /tmp/current
|
||||||
|
</example>
|
||||||
|
|
||||||
|
After these commands, **ledger-2003.dat** will contain all the
|
||||||
|
transactions up to year 2004, with **ledger.dat** containing only those
|
||||||
|
since 2004. However, the balances reported from **ledger.dat** will still
|
||||||
|
be the same.
|
||||||
|
|
||||||
|
Sometimes you will not want to carry forward certain balances, such as
|
||||||
|
those for Expense and Income. To do this, change the second command
|
||||||
|
above to:
|
||||||
|
|
||||||
|
<example>
|
||||||
|
ledger -e 2004/1/1 equity -^Income -^Expenses > /tmp/balances
|
||||||
|
</example>
|
||||||
|
|
||||||
|
*** entry
|
||||||
|
|
||||||
The three most laborious tasks of keeping a ledger are: adding new
|
The three most laborious tasks of keeping a ledger are: adding new
|
||||||
entries, reconciling accounts, and generating reports. To address the
|
entries, reconciling accounts, and generating reports. To address the
|
||||||
|
|
@ -613,6 +704,110 @@ There is a shell script in the distribution called "entry", which
|
||||||
simplifies the task of adding a new entry to your ledger, and then
|
simplifies the task of adding a new entry to your ledger, and then
|
||||||
launches =vi= to let you confirm that the entry looks appropriate.
|
launches =vi= to let you confirm that the entry looks appropriate.
|
||||||
|
|
||||||
|
** Option summary
|
||||||
|
|
||||||
|
-B ::
|
||||||
|
When printing accounts containing commodities, display the base
|
||||||
|
price for the commodity, rather than the quantity of that commodity
|
||||||
|
(the default) or its current price (if -P is used).
|
||||||
|
|
||||||
|
-b DATE ::
|
||||||
|
Only consider entries occuring on or after the given date.
|
||||||
|
|
||||||
|
-e DATE ::
|
||||||
|
Only consider entries occuring before the given date. The date is
|
||||||
|
not inclusive, so any entries occurring on that date will not be
|
||||||
|
used.
|
||||||
|
|
||||||
|
-c ::
|
||||||
|
Only consider entries occurring on or before the current date.
|
||||||
|
|
||||||
|
-C ::
|
||||||
|
Only consider entries whose cleared flag has been set. The default
|
||||||
|
is to consider both.
|
||||||
|
|
||||||
|
-d DATE ::
|
||||||
|
Only consider entries fitting the given date mask. DATE in this
|
||||||
|
case may be the name of a month, or a year, or a year and month,
|
||||||
|
such as "2004/05". It's a shorthand for having to specify -b and -e
|
||||||
|
together.
|
||||||
|
|
||||||
|
-E ::
|
||||||
|
Also show empty accounts in the balance totals report.
|
||||||
|
|
||||||
|
-f FILE[=ACCOUNT] ::
|
||||||
|
Read ledger entries from FILE. This takes precedence over the
|
||||||
|
environment variable LEDGER. If "=ACCOUNT" is appended to the
|
||||||
|
filename, then all of the entries are seen as if the transactions
|
||||||
|
accounts were prefixed by "ACCOUNT:". There may be multiple
|
||||||
|
occurrences of the -f option.
|
||||||
|
|
||||||
|
-F ::
|
||||||
|
Print full account names in all cases, such as "Assets:Checking"
|
||||||
|
instead of just "Checking". Only used current by the "balance"
|
||||||
|
command.
|
||||||
|
|
||||||
|
-h ::
|
||||||
|
Print out quick help on the various options and commands.
|
||||||
|
|
||||||
|
-i FILE ::
|
||||||
|
Read in the list of patterns to include/exclude from FILE.
|
||||||
|
Ordinarily, these are specified as arguments after the command.
|
||||||
|
|
||||||
|
-M ::
|
||||||
|
When used with the "register" command, causes only monthly subtotals
|
||||||
|
to appear. This can be useful for looking at spending patterns.
|
||||||
|
TODO: Accept an argument which specifies the period to use.
|
||||||
|
|
||||||
|
-G ::
|
||||||
|
Modifies the output generated by -M to be friendly to programs like
|
||||||
|
Gnuplot. It strips away the commodity label, and outputs only two
|
||||||
|
columns: the date and the amount.
|
||||||
|
|
||||||
|
-n ::
|
||||||
|
Do not show subtotals in the balance report, or split transactions
|
||||||
|
in the register report.
|
||||||
|
|
||||||
|
-N REGEXP ::
|
||||||
|
If an account matches REGEXP, only display it in the balance report
|
||||||
|
if its total is negative. Useful to avoid seeing credit in accounts
|
||||||
|
where one cannot spend that credit, and it will soon become negative
|
||||||
|
anyway (such as credit cards).
|
||||||
|
|
||||||
|
-p ARG ::
|
||||||
|
If a string, such as "COMM=$1.20", the commodity COMM will be
|
||||||
|
reported only in terms of its translated dollar value. This can be
|
||||||
|
used to perform arbitrary value substitutions. For example, to
|
||||||
|
report the value of your dollars in terms of the ounces of gold they
|
||||||
|
would buy, use: -p "$=0.00280112 AU" (or whatever the current
|
||||||
|
exchange rate is).
|
||||||
|
|
||||||
|
-P ::
|
||||||
|
Download current prices for all commodities by calling the script
|
||||||
|
"getquote". There is a "getquote" script included with ledger,
|
||||||
|
although any similar program could be used. It must take a single
|
||||||
|
argument, the name of the commodity, and must report the value as a
|
||||||
|
single amount, such as would appear in a ledger file. If the
|
||||||
|
commodity has no price, nothing should be output and the exit code
|
||||||
|
should be set to a non-zero value.
|
||||||
|
|
||||||
|
-R ::
|
||||||
|
Ignore all virtual transactions, and report only the real balance
|
||||||
|
for each account.
|
||||||
|
|
||||||
|
-s ::
|
||||||
|
If an account has children, show them in the balance report.
|
||||||
|
|
||||||
|
-S ::
|
||||||
|
Sort the ledger after reading it. This may affect "register" and
|
||||||
|
"print" output.
|
||||||
|
|
||||||
|
-U ::
|
||||||
|
Show only uncleared transactions. The default is to consider both.
|
||||||
|
|
||||||
|
-v ::
|
||||||
|
Display the version of ledger being used.
|
||||||
|
|
||||||
* Using Emacs to Keep Your Ledger
|
* Using Emacs to Keep Your Ledger
|
||||||
|
|
||||||
In the Ledger tarball is an Emacs module, =ledger.el=. This module
|
In the Ledger tarball is an Emacs module, =ledger.el=. This module
|
||||||
|
|
@ -648,10 +843,10 @@ C-c C-r ::
|
||||||
|
|
||||||
* Using GnuCash to Keep Your Ledger
|
* Using GnuCash to Keep Your Ledger
|
||||||
|
|
||||||
The Ledger tool is fast and simple, but it gives you no special
|
The Ledger tool is fast and simple, but it offers no custom method for
|
||||||
method of actually editing the ledger. It assumes you know how to use
|
actually editing the ledger. It assumes you know how to use a text
|
||||||
a text editor, and like doing so. Perhaps an Emacs mode will appear
|
editor, and like doing so. Perhaps an Emacs mode will appear someday
|
||||||
someday soon to make editing Ledger's data files much easier.
|
soon to make editing Ledger's data files much easier.
|
||||||
|
|
||||||
Until then, you are free to use GnuCash to maintain your ledger, and
|
Until then, you are free to use GnuCash to maintain your ledger, and
|
||||||
the Ledger program for querying and reporting on the contents
|
the Ledger program for querying and reporting on the contents
|
||||||
|
|
@ -661,6 +856,95 @@ that GnuCash uses, but the end result is identical.
|
||||||
Then again, why would anyone use a Gnome-centric, 35 megabyte behemoth
|
Then again, why would anyone use a Gnome-centric, 35 megabyte behemoth
|
||||||
to edit their data, and a 65 kilobyte binary to query it...
|
to edit their data, and a 65 kilobyte binary to query it...
|
||||||
|
|
||||||
|
* Using timeclock to record billable time
|
||||||
|
|
||||||
|
The timeclock tool makes it easy to track time events, like clocking
|
||||||
|
into and out of a particular job. These events accumulate in a
|
||||||
|
timelog file.
|
||||||
|
|
||||||
|
Each in/out event may have an optional description. If the "in"
|
||||||
|
description is a ledger account name, these in/out pairs may be viewed
|
||||||
|
as virtual transactions, adding time commodities (hours) to that
|
||||||
|
account.
|
||||||
|
|
||||||
|
For example, the command-line version of the timeclock tool (which is
|
||||||
|
written in Python) could be used to begin a timelog file like:
|
||||||
|
|
||||||
|
<example>
|
||||||
|
$ export TIMELOG=$HOME/.timelog
|
||||||
|
$ ti ClientOne category
|
||||||
|
$ sleep 10
|
||||||
|
$ to waited for ten seconds
|
||||||
|
</example>
|
||||||
|
|
||||||
|
The **.timelog** file now contains:
|
||||||
|
|
||||||
|
<example>
|
||||||
|
i 2004/10/06 15:21:00 ClientOne category
|
||||||
|
o 2004/10/06 15:21:10 waited for ten seconds
|
||||||
|
</example>
|
||||||
|
|
||||||
|
Ledger can parse this directly, as if it had seen the following ledger
|
||||||
|
entry:
|
||||||
|
|
||||||
|
<example>
|
||||||
|
2004/10/06 category
|
||||||
|
(ClientOne) 0.00277h
|
||||||
|
</example>
|
||||||
|
|
||||||
|
In other words, the timelog event pair is seen as adding 0.00277h (ten
|
||||||
|
seconds) worth of time to the ClientOne account. This would be
|
||||||
|
considered billable time, which later could be invoiced and credited
|
||||||
|
to accounts receivable:
|
||||||
|
|
||||||
|
<example>
|
||||||
|
2004/11/01 (INV#1) ClientOne, Inc.
|
||||||
|
Receivable:ClientOne $0.10
|
||||||
|
ClientOne -0.00277h @ $35.00
|
||||||
|
</example>
|
||||||
|
|
||||||
|
The above transaction converts the clocked time into an invoice for
|
||||||
|
the time spent, at an hourly rate of $35. Once the invoice is paid,
|
||||||
|
the money is deposited from the receivable account into a checking
|
||||||
|
account:
|
||||||
|
|
||||||
|
<example>
|
||||||
|
2004/12/01 ClientOne, Inc.
|
||||||
|
Assets:Checking $0.10
|
||||||
|
Receivable:ClientOne
|
||||||
|
</example>
|
||||||
|
|
||||||
|
And now the time spent has been turned into hard cash in the checking
|
||||||
|
account.
|
||||||
|
|
||||||
|
The advantage to using timeclock and invoicing to bill time is that
|
||||||
|
you will always know, by looking at the balance report, exactly how
|
||||||
|
much unbilled and unpaid time you've spent working for any particular
|
||||||
|
client.
|
||||||
|
|
||||||
|
I like to =!include= my timelog at the top of my company's accounting
|
||||||
|
ledger, with the attached prefix "Billable":
|
||||||
|
|
||||||
|
<example>
|
||||||
|
; -*-ledger-*-
|
||||||
|
|
||||||
|
; This is the ledger file for my company. But first, include the
|
||||||
|
; timelog data, entering all of the time events within the umbrella
|
||||||
|
; account "Billable".
|
||||||
|
|
||||||
|
!include /home/johnw/.timelog Billable
|
||||||
|
|
||||||
|
; Here follows this fiscal year's transactions for the company.
|
||||||
|
|
||||||
|
2004/11/01 (INV#1) ClientOne, Inc.
|
||||||
|
Receivable:ClientOne $0.10
|
||||||
|
Billable:ClientOne -0.00277h @ $35.00
|
||||||
|
|
||||||
|
2004/12/01 ClientOne, Inc.
|
||||||
|
Assets:Checking $0.10
|
||||||
|
Receivable:ClientOne
|
||||||
|
</example>
|
||||||
|
|
||||||
Footnotes:
|
Footnotes:
|
||||||
[1] In some special cases, it will automatically balance the entry
|
[1] In some special cases, it will automatically balance the entry
|
||||||
for you.
|
for you.
|
||||||
|
|
|
||||||
71
amount.cc
71
amount.cc
|
|
@ -55,7 +55,7 @@ class gmp_amount : public amount
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual amount * copy() const;
|
virtual amount * copy() const;
|
||||||
virtual amount * value(amount *) const;
|
virtual amount * value(const amount *) const;
|
||||||
virtual amount * street(bool get_quotes) const;
|
virtual amount * street(bool get_quotes) const;
|
||||||
virtual bool has_price() const {
|
virtual bool has_price() const {
|
||||||
return priced;
|
return priced;
|
||||||
|
|
@ -63,6 +63,8 @@ class gmp_amount : public amount
|
||||||
virtual void set_value(const amount * val);
|
virtual void set_value(const amount * val);
|
||||||
|
|
||||||
virtual bool is_zero() const;
|
virtual bool is_zero() const;
|
||||||
|
virtual bool is_negative() const;
|
||||||
|
virtual int compare(const amount * other) const;
|
||||||
|
|
||||||
virtual void negate() {
|
virtual void negate() {
|
||||||
mpz_ui_sub(quantity, 0, quantity);
|
mpz_ui_sub(quantity, 0, quantity);
|
||||||
|
|
@ -156,10 +158,10 @@ amount * gmp_amount::copy() const
|
||||||
return new_amt;
|
return new_amt;
|
||||||
}
|
}
|
||||||
|
|
||||||
amount * gmp_amount::value(amount * pr) const
|
amount * gmp_amount::value(const amount * pr) const
|
||||||
{
|
{
|
||||||
if (pr) {
|
if (pr) {
|
||||||
gmp_amount * p = dynamic_cast<gmp_amount *>(pr);
|
const gmp_amount * p = dynamic_cast<const gmp_amount *>(pr);
|
||||||
assert(p);
|
assert(p);
|
||||||
|
|
||||||
gmp_amount * new_amt = new gmp_amount();
|
gmp_amount * new_amt = new gmp_amount();
|
||||||
|
|
@ -298,6 +300,27 @@ bool gmp_amount::is_zero() const
|
||||||
return zero;
|
return zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool gmp_amount::is_negative() const
|
||||||
|
{
|
||||||
|
return mpz_sgn(quantity) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int gmp_amount::compare(const amount * other) const
|
||||||
|
{
|
||||||
|
amount * revalued = copy();
|
||||||
|
amount * copied = other->copy();
|
||||||
|
revalued->negate();
|
||||||
|
copied->credit(revalued);
|
||||||
|
delete revalued;
|
||||||
|
int result = 1;
|
||||||
|
if (copied->is_zero())
|
||||||
|
result = 0;
|
||||||
|
else if (copied->is_negative())
|
||||||
|
result = -1;
|
||||||
|
delete copied;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
static std::string amount_to_str(const commodity * comm, const mpz_t val,
|
static std::string amount_to_str(const commodity * comm, const mpz_t val,
|
||||||
bool full_precision)
|
bool full_precision)
|
||||||
{
|
{
|
||||||
|
|
@ -316,6 +339,9 @@ static std::string amount_to_str(const commodity * comm, const mpz_t val,
|
||||||
mpz_init(remainder);
|
mpz_init(remainder);
|
||||||
mpz_init(divisor);
|
mpz_init(divisor);
|
||||||
|
|
||||||
|
if (comm == NULL)
|
||||||
|
full_precision = true;
|
||||||
|
|
||||||
if (! full_precision && comm->precision < MAX_PRECISION)
|
if (! full_precision && comm->precision < MAX_PRECISION)
|
||||||
round(temp, temp, comm->precision);
|
round(temp, temp, comm->precision);
|
||||||
|
|
||||||
|
|
@ -337,7 +363,7 @@ static std::string amount_to_str(const commodity * comm, const mpz_t val,
|
||||||
|
|
||||||
std::ostringstream s;
|
std::ostringstream s;
|
||||||
|
|
||||||
if (comm->prefix) {
|
if (comm && comm->prefix) {
|
||||||
s << comm->symbol;
|
s << comm->symbol;
|
||||||
if (comm->separate)
|
if (comm->separate)
|
||||||
s << " ";
|
s << " ";
|
||||||
|
|
@ -348,7 +374,7 @@ static std::string amount_to_str(const commodity * comm, const mpz_t val,
|
||||||
|
|
||||||
if (mpz_sgn(quotient) == 0)
|
if (mpz_sgn(quotient) == 0)
|
||||||
s << '0';
|
s << '0';
|
||||||
else if (! comm->thousands)
|
else if (! comm || ! comm->thousands)
|
||||||
s << quotient;
|
s << quotient;
|
||||||
else {
|
else {
|
||||||
bool printed = false;
|
bool printed = false;
|
||||||
|
|
@ -371,22 +397,15 @@ static std::string amount_to_str(const commodity * comm, const mpz_t val,
|
||||||
s << temp;
|
s << temp;
|
||||||
|
|
||||||
if (powers > 0) {
|
if (powers > 0) {
|
||||||
if (comm->european)
|
s << (comm && comm->european ? '.' : ',');
|
||||||
s << ".";
|
|
||||||
else
|
|
||||||
s << ",";
|
|
||||||
|
|
||||||
printed = true;
|
printed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (comm->european)
|
s << (comm && comm->european ? ',' : '.');
|
||||||
s << ',';
|
|
||||||
else
|
|
||||||
s << '.';
|
|
||||||
|
|
||||||
if (! full_precision || mpz_sgn(rquotient) == 0) {
|
if (comm && (! full_precision || mpz_sgn(rquotient) == 0)) {
|
||||||
s.width(comm->precision);
|
s.width(comm->precision);
|
||||||
s.fill('0');
|
s.fill('0');
|
||||||
s << rquotient;
|
s << rquotient;
|
||||||
|
|
@ -399,17 +418,19 @@ static std::string amount_to_str(const commodity * comm, const mpz_t val,
|
||||||
|
|
||||||
width = MAX_PRECISION - width;
|
width = MAX_PRECISION - width;
|
||||||
|
|
||||||
while (p >= buf && *p == '0' &&
|
if (comm) {
|
||||||
(p - buf) >= (comm->precision - width))
|
while (p >= buf && *p == '0' &&
|
||||||
p--;
|
(p - buf) >= (comm->precision - width))
|
||||||
*(p + 1) = '\0';
|
p--;
|
||||||
|
*(p + 1) = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
s.width(width + std::strlen(buf));
|
s.width(width + std::strlen(buf));
|
||||||
s.fill('0');
|
s.fill('0');
|
||||||
s << buf;
|
s << buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! comm->prefix) {
|
if (comm && ! comm->prefix) {
|
||||||
if (comm->separate)
|
if (comm->separate)
|
||||||
s << " ";
|
s << " ";
|
||||||
s << comm->symbol;
|
s << comm->symbol;
|
||||||
|
|
@ -428,17 +449,11 @@ const std::string gmp_amount::as_str(bool full_prec) const
|
||||||
{
|
{
|
||||||
std::ostringstream s;
|
std::ostringstream s;
|
||||||
|
|
||||||
if (quantity_comm)
|
s << amount_to_str(quantity_comm, quantity, full_prec);
|
||||||
s << amount_to_str(quantity_comm, quantity, full_prec);
|
|
||||||
else
|
|
||||||
s << quantity;
|
|
||||||
|
|
||||||
if (priced) {
|
if (priced) {
|
||||||
s << " @ ";
|
s << " @ ";
|
||||||
if (price_comm)
|
s << amount_to_str(price_comm, price, full_prec);
|
||||||
s << amount_to_str(price_comm, price, full_prec);
|
|
||||||
else
|
|
||||||
s << price;
|
|
||||||
}
|
}
|
||||||
return s.str();
|
return s.str();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
108
ledger.cc
108
ledger.cc
|
|
@ -213,9 +213,9 @@ bool entry::finalize(bool do_compute)
|
||||||
(*x)->acct->balance.credit((*x)->cost);
|
(*x)->acct->balance.credit((*x)->cost);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If virtual accounts are being supported, walk through the
|
// If automated transactions are being used, walk through the
|
||||||
// transactions and create new virtual transactions for all that
|
// current transaction lines and create new transactions for all
|
||||||
// apply.
|
// that match.
|
||||||
|
|
||||||
for (book::virtual_map_iterator m = ledger->virtual_mapping.begin();
|
for (book::virtual_map_iterator m = ledger->virtual_mapping.begin();
|
||||||
m != ledger->virtual_mapping.end();
|
m != ledger->virtual_mapping.end();
|
||||||
|
|
@ -278,7 +278,7 @@ bool entry::finalize(bool do_compute)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool entry::matches(const regexps_map& regexps) const
|
bool entry::matches(const regexps_list& regexps) const
|
||||||
{
|
{
|
||||||
if (regexps.empty() || (ledger::matches(regexps, code) ||
|
if (regexps.empty() || (ledger::matches(regexps, code) ||
|
||||||
ledger::matches(regexps, desc))) {
|
ledger::matches(regexps, desc))) {
|
||||||
|
|
@ -290,7 +290,7 @@ bool entry::matches(const regexps_map& regexps) const
|
||||||
for (std::list<transaction *>::const_iterator x = xacts.begin();
|
for (std::list<transaction *>::const_iterator x = xacts.begin();
|
||||||
x != xacts.end();
|
x != xacts.end();
|
||||||
x++) {
|
x++) {
|
||||||
if (ledger::matches(regexps, (*x)->acct->name) ||
|
if (ledger::matches(regexps, (*x)->acct->as_str()) ||
|
||||||
ledger::matches(regexps, (*x)->note)) {
|
ledger::matches(regexps, (*x)->note)) {
|
||||||
match = true;
|
match = true;
|
||||||
break;
|
break;
|
||||||
|
|
@ -331,6 +331,12 @@ void totals::credit(const totals& other)
|
||||||
credit((*i).second);
|
credit((*i).second);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void totals::negate()
|
||||||
|
{
|
||||||
|
for (const_iterator i = amounts.begin(); i != amounts.end(); i++)
|
||||||
|
(*i).second->negate();
|
||||||
|
}
|
||||||
|
|
||||||
bool totals::is_zero() const
|
bool totals::is_zero() const
|
||||||
{
|
{
|
||||||
for (const_iterator i = amounts.begin(); i != amounts.end(); i++)
|
for (const_iterator i = amounts.begin(); i != amounts.end(); i++)
|
||||||
|
|
@ -339,6 +345,19 @@ bool totals::is_zero() const
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool totals::is_negative() const
|
||||||
|
{
|
||||||
|
bool all_negative = true;
|
||||||
|
bool some_negative = false;
|
||||||
|
for (const_iterator i = amounts.begin(); i != amounts.end(); i++) {
|
||||||
|
if ((*i).second->is_negative())
|
||||||
|
some_negative = true;
|
||||||
|
else if (! (*i).second->is_zero())
|
||||||
|
all_negative = false;
|
||||||
|
}
|
||||||
|
return some_negative && all_negative;
|
||||||
|
}
|
||||||
|
|
||||||
void totals::print(std::ostream& out, int width) const
|
void totals::print(std::ostream& out, int width) const
|
||||||
{
|
{
|
||||||
bool first = true;
|
bool first = true;
|
||||||
|
|
@ -365,10 +384,12 @@ account::~account()
|
||||||
delete (*i).second;
|
delete (*i).second;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string account::as_str() const
|
const std::string account::as_str(const account * stop) const
|
||||||
{
|
{
|
||||||
if (! parent)
|
if (! parent || this == stop)
|
||||||
return name;
|
return name;
|
||||||
|
else if (stop)
|
||||||
|
return parent->as_str(stop) + ":" + name;
|
||||||
else if (full_name.empty())
|
else if (full_name.empty())
|
||||||
full_name = parent->as_str() + ":" + name;
|
full_name = parent->as_str() + ":" + name;
|
||||||
|
|
||||||
|
|
@ -409,20 +430,6 @@ mask::mask(const mask& m) : exclude(m.exclude), pattern(m.pattern)
|
||||||
assert(regexp);
|
assert(regexp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void read_regexps(const std::string& path, regexps_map& regexps)
|
|
||||||
{
|
|
||||||
if (access(path.c_str(), R_OK) != -1) {
|
|
||||||
std::ifstream file(path.c_str());
|
|
||||||
|
|
||||||
while (! file.eof()) {
|
|
||||||
char buf[80];
|
|
||||||
file.getline(buf, 79);
|
|
||||||
if (*buf && ! std::isspace(*buf))
|
|
||||||
regexps.push_back(mask(buf));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool mask::match(const std::string& str) const
|
bool mask::match(const std::string& str) const
|
||||||
{
|
{
|
||||||
static int ovec[30];
|
static int ovec[30];
|
||||||
|
|
@ -431,67 +438,36 @@ bool mask::match(const std::string& str) const
|
||||||
return result >= 0 && ! exclude;
|
return result >= 0 && ! exclude;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool matches(const regexps_map& regexps, const std::string& str,
|
bool matches(const regexps_list& regexps, const std::string& str,
|
||||||
bool * by_exclusion)
|
bool * by_exclusion)
|
||||||
{
|
{
|
||||||
assert(! regexps.empty());
|
if (regexps.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
bool match = false;
|
bool match = false;
|
||||||
bool definite = false;
|
bool definite = false;
|
||||||
|
|
||||||
// std::ofstream out("regex.out", std::ios_base::app);
|
for (regexps_list_const_iterator r = regexps.begin();
|
||||||
// out << "Matching against: " << str << std::endl;
|
|
||||||
|
|
||||||
for (regexps_map_const_iterator r = regexps.begin();
|
|
||||||
r != regexps.end();
|
r != regexps.end();
|
||||||
r++) {
|
r++) {
|
||||||
// out << " Trying: " << (*r).pattern << std::endl;
|
|
||||||
|
|
||||||
static int ovec[30];
|
static int ovec[30];
|
||||||
int result = pcre_exec((*r).regexp, NULL, str.c_str(), str.length(),
|
int result = pcre_exec((*r).regexp, NULL, str.c_str(), str.length(),
|
||||||
0, 0, ovec, 30);
|
0, 0, ovec, 30);
|
||||||
if (result >= 0) {
|
if (result >= 0) {
|
||||||
// out << " Definite ";
|
match = ! (*r).exclude;
|
||||||
|
definite = true;
|
||||||
match = ! (*r).exclude;
|
}
|
||||||
// if (match)
|
else if ((*r).exclude) {
|
||||||
// out << "match";
|
if (! match)
|
||||||
// else
|
match = ! definite;
|
||||||
// out << "unmatch";
|
}
|
||||||
|
else {
|
||||||
definite = true;
|
definite = true;
|
||||||
} else {
|
|
||||||
assert(result == -1);
|
|
||||||
|
|
||||||
// out << " failure code = " << result << std::endl;
|
|
||||||
|
|
||||||
if ((*r).exclude) {
|
|
||||||
if (! match) {
|
|
||||||
match = ! definite;
|
|
||||||
// if (match)
|
|
||||||
// out << " indefinite match by exclusion" << std::endl;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
definite = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// out << " Current status: "
|
|
||||||
// << (definite ? "definite " : "")
|
|
||||||
// << (match ? "match" : "not match") << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (by_exclusion) {
|
|
||||||
if (match && ! definite && by_exclusion) {
|
|
||||||
// out << " Note: Matched by exclusion rule" << std::endl;
|
|
||||||
*by_exclusion = true;
|
|
||||||
} else {
|
|
||||||
*by_exclusion = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// out << " Final result: " << (match ? "match" : "not match")
|
if (by_exclusion)
|
||||||
// << std::endl;
|
*by_exclusion = match && ! definite && by_exclusion;
|
||||||
|
|
||||||
return match;
|
return match;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
143
ledger.el
143
ledger.el
|
|
@ -46,6 +46,13 @@
|
||||||
:type 'file
|
:type 'file
|
||||||
:group 'ledger)
|
:group 'ledger)
|
||||||
|
|
||||||
|
(defvar bold 'bold)
|
||||||
|
|
||||||
|
(defvar ledger-font-lock-keywords
|
||||||
|
'(("^[0-9./]+\\s-+\\(?:([^)]+)\\s-+\\)?\\([^*].+\\)" 1 bold)
|
||||||
|
("^\\s-+.+?\\( \\|\t\\|\\s-+$\\)" . font-lock-keyword-face))
|
||||||
|
"Default expressions to highlight in Ledger mode.")
|
||||||
|
|
||||||
(defun ledger-iterate-entries (callback)
|
(defun ledger-iterate-entries (callback)
|
||||||
(goto-char (point-min))
|
(goto-char (point-min))
|
||||||
(let* ((now (current-time))
|
(let* ((now (current-time))
|
||||||
|
|
@ -86,12 +93,15 @@
|
||||||
(list (read-string "Entry: " (format-time-string "%Y/%m/%d "))))
|
(list (read-string "Entry: " (format-time-string "%Y/%m/%d "))))
|
||||||
(let* ((args (mapcar 'shell-quote-argument (split-string entry)))
|
(let* ((args (mapcar 'shell-quote-argument (split-string entry)))
|
||||||
(date (car args))
|
(date (car args))
|
||||||
exit-code)
|
(insert-year t) exit-code)
|
||||||
(if (string-match "\\([0-9]+\\)/\\([0-9]+\\)/\\([0-9]+\\)" date)
|
(if (string-match "\\([0-9]+\\)/\\([0-9]+\\)/\\([0-9]+\\)" date)
|
||||||
(setq date (encode-time 0 0 0 (string-to-int (match-string 3 date))
|
(setq date (encode-time 0 0 0 (string-to-int (match-string 3 date))
|
||||||
(string-to-int (match-string 2 date))
|
(string-to-int (match-string 2 date))
|
||||||
(string-to-int (match-string 1 date)))))
|
(string-to-int (match-string 1 date)))))
|
||||||
(ledger-find-slot date)
|
(ledger-find-slot date)
|
||||||
|
(save-excursion
|
||||||
|
(if (re-search-backward "^Y " nil t)
|
||||||
|
(setq insert-year nil)))
|
||||||
(save-excursion
|
(save-excursion
|
||||||
(insert
|
(insert
|
||||||
(with-temp-buffer
|
(with-temp-buffer
|
||||||
|
|
@ -99,8 +109,11 @@
|
||||||
(apply 'call-process ledger-binary-path nil t nil
|
(apply 'call-process ledger-binary-path nil t nil
|
||||||
(cons "entry" args)))
|
(cons "entry" args)))
|
||||||
(if (= 0 exit-code)
|
(if (= 0 exit-code)
|
||||||
(buffer-substring (+ (point-min) 5) (point-max))
|
(if insert-year
|
||||||
(concat (substring entry 5) "\n\n")))))))
|
(buffer-string)
|
||||||
|
(buffer-substring 5 (point-max)))
|
||||||
|
(concat (if insert-year entry
|
||||||
|
(substring entry 5)) "\n\n")))))))
|
||||||
|
|
||||||
(defun ledger-expand-entry ()
|
(defun ledger-expand-entry ()
|
||||||
(interactive)
|
(interactive)
|
||||||
|
|
@ -124,13 +137,23 @@
|
||||||
(setq clear t))))
|
(setq clear t))))
|
||||||
clear))
|
clear))
|
||||||
|
|
||||||
|
(defun ledger-print-result (command)
|
||||||
|
(interactive "sLedger command: ")
|
||||||
|
(shell-command (format "%s -f %s %s" ledger-binary-path
|
||||||
|
buffer-file-name command)))
|
||||||
|
|
||||||
(define-derived-mode ledger-mode text-mode "Ledger"
|
(define-derived-mode ledger-mode text-mode "Ledger"
|
||||||
"A mode for editing ledger data files."
|
"A mode for editing ledger data files."
|
||||||
(setq comment-start ";" comment-end nil
|
(set (make-local-variable 'comment-start) ";")
|
||||||
indent-tabs-mode nil)
|
(set (make-local-variable 'comment-end) "")
|
||||||
|
(set (make-local-variable 'indent-tabs-mode) nil)
|
||||||
|
(if (boundp 'font-lock-defaults)
|
||||||
|
(set (make-local-variable 'font-lock-defaults)
|
||||||
|
'(ledger-font-lock-keywords nil t)))
|
||||||
(let ((map (current-local-map)))
|
(let ((map (current-local-map)))
|
||||||
(define-key map [(control ?c) (control ?a)] 'ledger-add-entry)
|
(define-key map [(control ?c) (control ?a)] 'ledger-add-entry)
|
||||||
(define-key map [(control ?c) (control ?c)] 'ledger-toggle-current)
|
(define-key map [(control ?c) (control ?c)] 'ledger-toggle-current)
|
||||||
|
(define-key map [(control ?c) (control ?p)] 'ledger-print-result)
|
||||||
(define-key map [(control ?c) (control ?r)] 'ledger-reconcile)))
|
(define-key map [(control ?c) (control ?r)] 'ledger-reconcile)))
|
||||||
|
|
||||||
(defun ledger-parse-entries (account &optional all-p after-date)
|
(defun ledger-parse-entries (account &optional all-p after-date)
|
||||||
|
|
@ -144,12 +167,15 @@
|
||||||
(setq total 0.0)
|
(setq total 0.0)
|
||||||
(while (looking-at
|
(while (looking-at
|
||||||
(concat "\\s-+\\([A-Za-z_].+?\\)\\(\\s-*$\\| \\s-*"
|
(concat "\\s-+\\([A-Za-z_].+?\\)\\(\\s-*$\\| \\s-*"
|
||||||
"\\([^0-9]+\\)\\s-*\\([0-9.]+\\)\\)"))
|
"\\([^0-9]+\\)\\s-*\\([0-9,.]+\\)\\)?"
|
||||||
|
"\\(\\s-+;.+\\)?$"))
|
||||||
(let ((acct (match-string 1))
|
(let ((acct (match-string 1))
|
||||||
(amt (match-string 4)))
|
(amt (match-string 4)))
|
||||||
(if amt
|
(when amt
|
||||||
(setq amt (string-to-number amt)
|
(while (string-match "," amt)
|
||||||
total (+ total amt)))
|
(setq amt (replace-match "" nil nil amt)))
|
||||||
|
(setq amt (string-to-number amt)
|
||||||
|
total (+ total amt)))
|
||||||
(if (string= account acct)
|
(if (string= account acct)
|
||||||
(setq entries
|
(setq entries
|
||||||
(cons (list (copy-marker start)
|
(cons (list (copy-marker start)
|
||||||
|
|
@ -164,6 +190,11 @@
|
||||||
"A mode for reconciling ledger entries."
|
"A mode for reconciling ledger entries."
|
||||||
(let ((map (make-sparse-keymap)))
|
(let ((map (make-sparse-keymap)))
|
||||||
(define-key map [? ] 'ledger-reconcile-toggle)
|
(define-key map [? ] 'ledger-reconcile-toggle)
|
||||||
|
(define-key map [?q]
|
||||||
|
(function
|
||||||
|
(lambda ()
|
||||||
|
(interactive)
|
||||||
|
(kill-buffer (current-buffer)))))
|
||||||
(use-local-map map)))
|
(use-local-map map)))
|
||||||
|
|
||||||
(add-to-list 'minor-mode-alist
|
(add-to-list 'minor-mode-alist
|
||||||
|
|
@ -172,6 +203,23 @@
|
||||||
(defvar ledger-buf nil)
|
(defvar ledger-buf nil)
|
||||||
(defvar ledger-acct nil)
|
(defvar ledger-acct nil)
|
||||||
|
|
||||||
|
(defun ledger-update-balance-display ()
|
||||||
|
(let ((account ledger-acct))
|
||||||
|
(with-temp-buffer
|
||||||
|
(let ((exit-code
|
||||||
|
(apply 'call-process ledger-binary-path nil t nil
|
||||||
|
(list "-C" "balance" account))))
|
||||||
|
(if (/= 0 exit-code)
|
||||||
|
(setq ledger-reconcile-text "Reconcile [ERR]")
|
||||||
|
(goto-char (point-min))
|
||||||
|
(delete-horizontal-space)
|
||||||
|
(skip-syntax-forward "^ ")
|
||||||
|
(setq ledger-reconcile-text
|
||||||
|
(concat "Reconcile ["
|
||||||
|
(buffer-substring-no-properties (point-min) (point))
|
||||||
|
"]"))))))
|
||||||
|
(redraw-modeline))
|
||||||
|
|
||||||
(defun ledger-reconcile-toggle ()
|
(defun ledger-reconcile-toggle ()
|
||||||
(interactive)
|
(interactive)
|
||||||
(let ((where (get-text-property (point) 'where))
|
(let ((where (get-text-property (point) 'where))
|
||||||
|
|
@ -188,44 +236,59 @@
|
||||||
(remove-text-properties (line-beginning-position)
|
(remove-text-properties (line-beginning-position)
|
||||||
(line-end-position)
|
(line-end-position)
|
||||||
(list 'face)))
|
(list 'face)))
|
||||||
(with-temp-buffer
|
(forward-line)
|
||||||
(let ((exit-code
|
(ledger-update-balance-display)))
|
||||||
(apply 'call-process ledger-binary-path nil t nil
|
|
||||||
(list "-C" "balance" account))))
|
|
||||||
(if (/= 0 exit-code)
|
|
||||||
(setq ledger-reconcile-text "Reconcile [ERR]")
|
|
||||||
(goto-char (point-min))
|
|
||||||
(delete-horizontal-space)
|
|
||||||
(skip-syntax-forward "^ ")
|
|
||||||
(setq ledger-reconcile-text
|
|
||||||
(concat "Reconcile ["
|
|
||||||
(buffer-substring-no-properties (point-min) (point))
|
|
||||||
"]")))))))
|
|
||||||
|
|
||||||
(defun ledger-reconcile (account)
|
(defun ledger-reconcile (account &optional days)
|
||||||
(interactive "sAccount to reconcile: ")
|
(interactive "sAccount to reconcile: \nnBack how far (default 30 days): ")
|
||||||
(let* ((then (time-subtract (current-time)
|
(let* ((then (time-subtract (current-time)
|
||||||
(seconds-to-time (* 90 24 60 60))))
|
(seconds-to-time (* (or days 30) 24 60 60))))
|
||||||
(items (save-excursion
|
(items (save-excursion
|
||||||
(goto-char (point-min))
|
(goto-char (point-min))
|
||||||
(ledger-parse-entries account t then)))
|
(ledger-parse-entries account t then)))
|
||||||
(buf (current-buffer)))
|
(buf (current-buffer)))
|
||||||
(pop-to-buffer (generate-new-buffer "*Reconcile*"))
|
(with-current-buffer
|
||||||
(ledger-reconcile-mode)
|
(pop-to-buffer (generate-new-buffer "*Reconcile*"))
|
||||||
(set (make-local-variable 'ledger-buf) buf)
|
(ledger-reconcile-mode)
|
||||||
(set (make-local-variable 'ledger-acct) account)
|
(set (make-local-variable 'ledger-buf) buf)
|
||||||
(dolist (item items)
|
(set (make-local-variable 'ledger-acct) account)
|
||||||
(let ((beg (point)))
|
(ledger-update-balance-display)
|
||||||
(insert (format "%s %-30s %8.2f\n"
|
(dolist (item items)
|
||||||
(format-time-string "%Y/%m/%d" (nth 2 item))
|
(let ((beg (point)))
|
||||||
(nth 3 item) (nth 4 item)))
|
(insert (format "%s %-30s %8.2f\n"
|
||||||
(if (nth 1 item)
|
(format-time-string "%Y/%m/%d" (nth 2 item))
|
||||||
|
(nth 3 item) (nth 4 item)))
|
||||||
|
(if (nth 1 item)
|
||||||
|
(set-text-properties beg (1- (point))
|
||||||
|
(list 'face 'bold
|
||||||
|
'where (nth 0 item)))
|
||||||
(set-text-properties beg (1- (point))
|
(set-text-properties beg (1- (point))
|
||||||
(list 'face 'bold
|
(list 'where (nth 0 item)))))
|
||||||
'where (nth 0 item)))
|
(goto-char (point-min))))))
|
||||||
(set-text-properties beg (1- (point))
|
|
||||||
(list 'where (nth 0 item)))))
|
(defun ledger-align-dollars (&optional column)
|
||||||
(goto-char (point-min)))))
|
(interactive "p")
|
||||||
|
(if (= column 1)
|
||||||
|
(setq column 48))
|
||||||
|
(while (search-forward "$" nil t)
|
||||||
|
(backward-char)
|
||||||
|
(let ((col (current-column))
|
||||||
|
(beg (point))
|
||||||
|
target-col len)
|
||||||
|
(skip-chars-forward "-$0-9,.")
|
||||||
|
(setq len (- (point) beg))
|
||||||
|
(setq target-col (- column len))
|
||||||
|
(if (< col target-col)
|
||||||
|
(progn
|
||||||
|
(goto-char beg)
|
||||||
|
(insert (make-string (- target-col col) ? )))
|
||||||
|
(move-to-column target-col)
|
||||||
|
(if (looking-back " ")
|
||||||
|
(delete-char (- col target-col))
|
||||||
|
(skip-chars-forward "^ \t")
|
||||||
|
(delete-horizontal-space)
|
||||||
|
(insert " ")))
|
||||||
|
(forward-line))))
|
||||||
|
|
||||||
(provide 'ledger)
|
(provide 'ledger)
|
||||||
|
|
||||||
|
|
|
||||||
66
ledger.h
66
ledger.h
|
|
@ -1,5 +1,5 @@
|
||||||
#ifndef _LEDGER_H
|
#ifndef _LEDGER_H
|
||||||
#define _LEDGER_H "$Revision: 1.29 $"
|
#define _LEDGER_H "$Revision: 1.34 $"
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
|
|
@ -73,15 +73,17 @@ class amount
|
||||||
virtual void set_commdty(commodity *) = 0;
|
virtual void set_commdty(commodity *) = 0;
|
||||||
|
|
||||||
virtual amount * copy() const = 0;
|
virtual amount * copy() const = 0;
|
||||||
virtual amount * value(amount * pr = NULL) const = 0;
|
virtual amount * value(const amount * pr = NULL) const = 0;
|
||||||
virtual amount * street(bool get_quotes) const = 0;
|
virtual amount * street(bool get_quotes) const = 0;
|
||||||
|
|
||||||
virtual bool has_price() const = 0;
|
virtual bool has_price() const = 0;
|
||||||
virtual void set_value(const amount * pr) = 0;
|
virtual void set_value(const amount * pr) = 0;
|
||||||
|
|
||||||
// Test if the quantity is zero
|
// Comparison
|
||||||
|
|
||||||
virtual bool is_zero() const = 0;
|
virtual bool is_zero() const = 0;
|
||||||
|
virtual bool is_negative() const = 0;
|
||||||
|
virtual int compare(const amount * other) const = 0;
|
||||||
|
|
||||||
// Assignment
|
// Assignment
|
||||||
|
|
||||||
|
|
@ -115,15 +117,9 @@ class mask
|
||||||
bool match(const std::string& str) const;
|
bool match(const std::string& str) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::list<mask> regexps_map;
|
typedef std::list<mask> regexps_list;
|
||||||
typedef std::list<mask>::iterator regexps_map_iterator;
|
typedef std::list<mask>::iterator regexps_list_iterator;
|
||||||
typedef std::list<mask>::const_iterator regexps_map_const_iterator;
|
typedef std::list<mask>::const_iterator regexps_list_const_iterator;
|
||||||
|
|
||||||
void record_regexp(const std::string& pattern, regexps_map& regexps);
|
|
||||||
void read_regexps(const std::string& path, regexps_map& regexps);
|
|
||||||
bool matches(const regexps_map& regexps, const std::string& str,
|
|
||||||
bool * by_exclusion = NULL);
|
|
||||||
|
|
||||||
|
|
||||||
class account;
|
class account;
|
||||||
class transaction
|
class transaction
|
||||||
|
|
@ -186,7 +182,7 @@ class entry
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool matches(const regexps_map& regexps) const;
|
bool matches(const regexps_list& regexps) const;
|
||||||
bool validate(bool show_unaccounted = false) const;
|
bool validate(bool show_unaccounted = false) const;
|
||||||
bool finalize(bool do_compute = false);
|
bool finalize(bool do_compute = false);
|
||||||
|
|
||||||
|
|
@ -223,7 +219,10 @@ class totals
|
||||||
void credit(const amount * val);
|
void credit(const amount * val);
|
||||||
void credit(const totals& other);
|
void credit(const totals& other);
|
||||||
|
|
||||||
|
void negate();
|
||||||
|
|
||||||
bool is_zero() const;
|
bool is_zero() const;
|
||||||
|
bool is_negative() const;
|
||||||
|
|
||||||
void print(std::ostream& out, int width) const;
|
void print(std::ostream& out, int width) const;
|
||||||
};
|
};
|
||||||
|
|
@ -238,15 +237,15 @@ class account
|
||||||
account(const account&);
|
account(const account&);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
account * parent;
|
account * parent;
|
||||||
|
|
||||||
std::string name;
|
std::string name;
|
||||||
#ifdef READ_GNUCASH
|
#ifdef READ_GNUCASH
|
||||||
commodity * comm; // default commodity for this account
|
commodity * comm; // default commodity for this account
|
||||||
#endif
|
#endif
|
||||||
totals balance; // optional, parse-time computed balance
|
totals balance; // optional, parse-time computed balance
|
||||||
int checked; // 'balance' uses this for speed's sake
|
int checked; // 'balance' uses this for speed's sake
|
||||||
accounts_map children;
|
accounts_map children;
|
||||||
|
|
||||||
mutable std::string full_name;
|
mutable std::string full_name;
|
||||||
|
|
||||||
|
|
@ -258,7 +257,7 @@ class account
|
||||||
|
|
||||||
~account();
|
~account();
|
||||||
|
|
||||||
const std::string as_str() const;
|
const std::string as_str(const account * stop = NULL) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -267,10 +266,10 @@ class book
|
||||||
book(const book&);
|
book(const book&);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
typedef std::map<regexps_map *,
|
typedef std::map<regexps_list *,
|
||||||
std::list<transaction *> *> virtual_map;
|
std::list<transaction *> *> virtual_map;
|
||||||
|
|
||||||
typedef std::pair<regexps_map *,
|
typedef std::pair<regexps_list *,
|
||||||
std::list<transaction *> *> virtual_map_pair;
|
std::list<transaction *> *> virtual_map_pair;
|
||||||
|
|
||||||
typedef virtual_map::const_iterator virtual_map_iterator;
|
typedef virtual_map::const_iterator virtual_map_iterator;
|
||||||
|
|
@ -289,7 +288,8 @@ class book
|
||||||
void sort(Compare comp) {
|
void sort(Compare comp) {
|
||||||
std::sort(entries.begin(), entries.end(), comp);
|
std::sort(entries.begin(), entries.end(), comp);
|
||||||
}
|
}
|
||||||
void print(std::ostream& out, regexps_map& regexps, bool shortcut) const;
|
void print(std::ostream& out, regexps_list& regexps,
|
||||||
|
bool shortcut) const;
|
||||||
|
|
||||||
account * re_find_account(const std::string& regex);
|
account * re_find_account(const std::string& regex);
|
||||||
account * find_account(const std::string& name, bool create = true);
|
account * find_account(const std::string& name, bool create = true);
|
||||||
|
|
@ -312,16 +312,30 @@ inline commodity::commodity(const std::string& sym, bool pre, bool sep,
|
||||||
|
|
||||||
// Parsing routines
|
// Parsing routines
|
||||||
|
|
||||||
extern book * parse_ledger(std::istream& in, regexps_map& regexps,
|
extern int parse_ledger(book * ledger, std::istream& in,
|
||||||
bool compute_balances);
|
regexps_list& regexps,
|
||||||
|
bool compute_balances = false,
|
||||||
|
const char * acct_prefix = NULL);
|
||||||
#ifdef READ_GNUCASH
|
#ifdef READ_GNUCASH
|
||||||
extern book * parse_gnucash(std::istream& in, bool compute_balances);
|
extern book * parse_gnucash(std::istream& in, bool compute_balances = false);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
extern int parse_ledger_file(book * ledger, const std::string& file,
|
||||||
|
regexps_list& regexps,
|
||||||
|
bool compute_balances = false,
|
||||||
|
const char * acct_prefix = NULL);
|
||||||
|
|
||||||
extern bool parse_date_mask(const char * date_str,
|
extern bool parse_date_mask(const char * date_str,
|
||||||
struct std::tm * result);
|
struct std::tm * result);
|
||||||
extern bool parse_date(const char * date_str, std::time_t * result,
|
extern bool parse_date(const char * date_str, std::time_t * result,
|
||||||
const int year = -1);
|
const int year = -1);
|
||||||
|
|
||||||
|
extern void record_regexp(const std::string& pattern, regexps_list& regexps);
|
||||||
|
extern void read_regexps(const std::string& path, regexps_list& regexps);
|
||||||
|
extern bool matches(const regexps_list& regexps, const std::string& str,
|
||||||
|
bool * by_exclusion = NULL);
|
||||||
|
|
||||||
|
extern void read_prices(const std::string& path);
|
||||||
extern void parse_price_setting(const std::string& setting);
|
extern void parse_price_setting(const std::string& setting);
|
||||||
|
|
||||||
} // namespace ledger
|
} // namespace ledger
|
||||||
|
|
|
||||||
738
ledger.texi
738
ledger.texi
|
|
@ -1,738 +0,0 @@
|
||||||
\input texinfo @c -*-texinfo-*-
|
|
||||||
|
|
||||||
@setfilename ledger.info
|
|
||||||
@settitle Ledger: Command-Line Accounting
|
|
||||||
|
|
||||||
@documentencoding iso-8859-1
|
|
||||||
|
|
||||||
@iftex
|
|
||||||
@finalout
|
|
||||||
@end iftex
|
|
||||||
|
|
||||||
@titlepage
|
|
||||||
@title Ledger: Command-Line Accounting
|
|
||||||
@author John Wiegley
|
|
||||||
@end titlepage
|
|
||||||
|
|
||||||
@node Top, Building, (dir), (dir)
|
|
||||||
@top Overview
|
|
||||||
@c Page published by Emacs Muse begins here
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@menu
|
|
||||||
* Building::
|
|
||||||
* Introduction::
|
|
||||||
* Keeping a ledger::
|
|
||||||
* Using the Ledger Tool::
|
|
||||||
* An easy way to add new entries::
|
|
||||||
* Using Emacs to Keep Your Ledger::
|
|
||||||
* Using GnuCash to Keep Your Ledger::
|
|
||||||
@end menu
|
|
||||||
|
|
||||||
@node Building, Introduction, Top, Top
|
|
||||||
@chapter Building
|
|
||||||
|
|
||||||
Welcome to ledger, a command-line accounting program that uses a
|
|
||||||
text-based ledger file, yet provides full double-entry accounting, use
|
|
||||||
of commodities, unlimited accounts, etc.
|
|
||||||
|
|
||||||
Ledger is written in ANSI C++, and should compile on any platform. It
|
|
||||||
only depends on the GNU multiprecision integer library (gmp, or
|
|
||||||
libgmp), and the Perl regular expression library (pcre, or libpcre).
|
|
||||||
Also, this project was developed using GNU make and gcc 3.3.
|
|
||||||
|
|
||||||
To build and install, once you have these libraries on your system,
|
|
||||||
enter these commands:
|
|
||||||
|
|
||||||
@example
|
|
||||||
make
|
|
||||||
cp ledger /usr/local/bin
|
|
||||||
@end example
|
|
||||||
|
|
||||||
@node Introduction, Keeping a ledger, Building, Top
|
|
||||||
@chapter Introduction
|
|
||||||
|
|
||||||
Ledger is an accounting tool with the moxie to exist. It provides not
|
|
||||||
one bell or whistle for the money, and returns the user to the days
|
|
||||||
before user interfaces were even a twinkle in their father's CRT.
|
|
||||||
|
|
||||||
What it does do is provide a double-entry accounting ledger with all
|
|
||||||
of the flexibility and muscle of its modern day cousins---without any
|
|
||||||
of the fat. Think of it as the bran muffin of accounting tools.
|
|
||||||
|
|
||||||
To begin with, you need to start keeping a ledger. This is the basis
|
|
||||||
of all accounting, and if you don't know how to do it, now is the time
|
|
||||||
to learn. The little booklet that comes with your checkbook is a
|
|
||||||
ledger, so we'll describe double-entry accounting in terms of that.
|
|
||||||
|
|
||||||
A checkbook ledger records debits (subtractions, or withdrawals) and
|
|
||||||
credits (additions, or deposits) with reference to a single account:
|
|
||||||
your checking account. Where the money comes from, and where it goes
|
|
||||||
to, are simply described in the memo field where you write the person
|
|
||||||
or the company's name. The ultimate aim of keeping a checkbook ledger
|
|
||||||
is so you know how much money is available to spend at all times.
|
|
||||||
That is really the aim of all ledgers.
|
|
||||||
|
|
||||||
What computers add is the ability to walk through all of those
|
|
||||||
transactions and tell you things about your spending habits; let you
|
|
||||||
devise budgets to get control over your spending; squirrel away money
|
|
||||||
into virtual savings account without having to physically move the
|
|
||||||
money around; etc. As you keep your checkbook ledger, you are
|
|
||||||
recording a lot of information about your life and your habits, and
|
|
||||||
sometimes that information can tell you things you aren't even aware
|
|
||||||
of. That is the aim of all good accounting tools.
|
|
||||||
|
|
||||||
The next step up from a checkbook ledger is a ledger that covers all
|
|
||||||
of your accounts, not just your checking account. In this ledger, you
|
|
||||||
write not only who the money goes to---in the case of a debit---but
|
|
||||||
where the money is coming from. In the checkbook ledger, its assumed
|
|
||||||
that all of the money is coming from your checking account. But in a
|
|
||||||
general ledger, you have to write two-lines: The source and target.
|
|
||||||
There must always be a debit from some account for any credit made to
|
|
||||||
anyone else. This is what is meant by ``double-entry'' accounting.
|
|
||||||
|
|
||||||
For example, let's say you have a checking account and a brokerage
|
|
||||||
account, and that you can write checks from both of them. Rather than
|
|
||||||
keeping two checkbooks, you decide to use one ledger for both. Once
|
|
||||||
you get the hang of this, you'll be ready to use one ledger for all of
|
|
||||||
your accounting needs, which gets you to the point of this
|
|
||||||
introduction.
|
|
||||||
|
|
||||||
So in your general ledger, you need to pay Pacific Bell Telephone for
|
|
||||||
your monthly phone bill. The cost is $23.00. In a checkbook ledger,
|
|
||||||
you would write out a line that credits your account with Pacific Bell
|
|
||||||
by $23 as follows:
|
|
||||||
|
|
||||||
@example
|
|
||||||
9/29 100 Pacific Bell $23.00 $77.00
|
|
||||||
@end example
|
|
||||||
|
|
||||||
Very simple: You've written check #100 for $23 to Pacific Bell, which
|
|
||||||
leaves your balance in checking at $77.
|
|
||||||
|
|
||||||
But in a general ledger, you need to say where the money is coming
|
|
||||||
from. A general ledger entry would look like this:
|
|
||||||
|
|
||||||
@example
|
|
||||||
9/29 100 Pacific Bell $23.00 $223.00
|
|
||||||
Checking $-23.00 $77.00
|
|
||||||
@end example
|
|
||||||
|
|
||||||
What does all of this mean? The first line shows a credit (or
|
|
||||||
payment) to Pacific Bell to the tune of $23.00. Then, because there
|
|
||||||
is no one ``balance'' in a general ledger, we've written in the total
|
|
||||||
balance of your payments to the account ``Pacific Bell''. This was done
|
|
||||||
by looking at the last entry for ``Pacific Bell'' in the general ledger,
|
|
||||||
adding $23.00 to that amount, and writing in the total in the balance
|
|
||||||
column.
|
|
||||||
|
|
||||||
Secondly, the money is coming from your ``Checking'' account, which
|
|
||||||
means a debit (or withdrawal) of $23.00, which will leave the ending
|
|
||||||
balance in your ``Checking'' account at $77.00.
|
|
||||||
|
|
||||||
The transaction itself must balance to $0: $23 goes to Pacific Bell,
|
|
||||||
$23 comes from Checking: there is nothing left over to be accounted
|
|
||||||
for. The money has in fact moved from one account to another. This
|
|
||||||
is basis of double-entry accounting: That money never pops out of
|
|
||||||
existence, it is always described as a transaction between accounts---
|
|
||||||
as a flow from one place to another.
|
|
||||||
|
|
||||||
Keeping a general ledger is the same as keeping two separate ledgers:
|
|
||||||
One for Pacific Bell and one for Checking. In that case, each time
|
|
||||||
you write a credit into one, you write a corresponding debit into the
|
|
||||||
other. This makes it much easier to write in the running balance,
|
|
||||||
since you don't have to go looking back for the last time an account
|
|
||||||
was referenced, but it also means having a lot of ledger books if you
|
|
||||||
deal with multiple accounts.
|
|
||||||
|
|
||||||
Enter the beauty of a computerized accounting tool. The purpose of
|
|
||||||
Ledger is to make general ledger accounting simple by keeping track of
|
|
||||||
the balances for you. Your only job is to enter credit/debit pairs
|
|
||||||
and make sure they balance. If a transaction does not balance, Ledger
|
|
||||||
will display an error and ignore the transaction.@footnote{In some special cases, it will automatically balance the entry
|
|
||||||
for you.}
|
|
||||||
|
|
||||||
Your usage of Ledger will have two parts: Keeping the ledger, and
|
|
||||||
using the Ledger tool to provide you with information summaries
|
|
||||||
derived from your ledger's entries.
|
|
||||||
|
|
||||||
@node Keeping a ledger, Using the Ledger Tool, Introduction, Top
|
|
||||||
@chapter Keeping a ledger
|
|
||||||
|
|
||||||
The most important part of accounting is keeping a good ledger. If
|
|
||||||
you have a good ledger, tools can be written to work whatever
|
|
||||||
mathematically tricks you need to better understand your spending
|
|
||||||
patterns. Without a good ledger, no tool, however smart, can help
|
|
||||||
you.
|
|
||||||
|
|
||||||
The Ledger program aims at making ledger entry as simple as possible.
|
|
||||||
Since it is a command-line tool, it does not provide a user interface
|
|
||||||
for keeping a ledger. If you like, you may use Gnucash to maintain
|
|
||||||
your ledger, in which case the Ledger program will read Gnucash's data
|
|
||||||
files directly. In that case, read the Gnucash manual now, and skip
|
|
||||||
to the next chapter.
|
|
||||||
|
|
||||||
If you are not using Gnucash, but a text editor to maintain your
|
|
||||||
ledger, read on. Ledger has been designed to make data entry as
|
|
||||||
simple as possible, by keeping the ledger format easy, and also by
|
|
||||||
automagically determining as much information as possible based on the
|
|
||||||
nature of your entries.
|
|
||||||
|
|
||||||
For example, you do not need to tell Ledger about the accounts you
|
|
||||||
use. Any time Ledger sees a debit or a credit to an account it knows
|
|
||||||
nothing about, it will create it. If you use a commodity that is new
|
|
||||||
to Ledger, it will create that commodity, and determine its display
|
|
||||||
characteristics (placement of the symbol before or after the amount,
|
|
||||||
display precision, etc) based on how you used the commodity in the
|
|
||||||
transaction.
|
|
||||||
|
|
||||||
Here is the Pacific Bell example from above, given as a Ledger
|
|
||||||
transaction:
|
|
||||||
|
|
||||||
@example
|
|
||||||
9/29 (100) Pacific Bell
|
|
||||||
Expenses:Utilities:Telephone $23.00
|
|
||||||
Assets:Checking $-23.00
|
|
||||||
@end example
|
|
||||||
|
|
||||||
As you can see, it is very similar to what would be written on paper,
|
|
||||||
minus the computed balance totals, and adding in account names that
|
|
||||||
work better with Ledger's scheme of things. In fact, since Ledger is
|
|
||||||
smart about many things, you don't need to specify the balanced
|
|
||||||
amount, if it is the same as the first line:
|
|
||||||
|
|
||||||
@example
|
|
||||||
9/29 (100) Pacific Bell
|
|
||||||
Expenses:Utilities:Telephone $23.00
|
|
||||||
Assets:Checking
|
|
||||||
@end example
|
|
||||||
|
|
||||||
For this entry, Ledger will figure out that $-23.00 must come from
|
|
||||||
``Assets:Checking'' in order to balance the entry.
|
|
||||||
|
|
||||||
@menu
|
|
||||||
* Credits and Debits::
|
|
||||||
* Commodities and Currencies::
|
|
||||||
* Accounts and Inventories::
|
|
||||||
* Understanding Equity::
|
|
||||||
@end menu
|
|
||||||
|
|
||||||
@node Credits and Debits, Commodities and Currencies, Keeping a ledger, Keeping a ledger
|
|
||||||
@section Credits and Debits
|
|
||||||
|
|
||||||
Credit and debit are simple enough terms in themselves, but the usages
|
|
||||||
of the modern world have made them very hard to puzzle out.
|
|
||||||
|
|
||||||
Basically, a credit means you add something to an account, and a debit
|
|
||||||
means you take away. A debit card is correctly name: From your point
|
|
||||||
of view, it debits your checking account every time you use it.
|
|
||||||
|
|
||||||
The credit card is strangely named, because you have to look at it
|
|
||||||
from the merchant's point of view: Every time you use it, it credit's
|
|
||||||
@emph{his} account right away. This was a giant leap from the days of cash
|
|
||||||
and checks, when the only other way to supply immediate credit was by
|
|
||||||
a wire transfer. But a credit card does not credit you anything at
|
|
||||||
all. In fact, from your point of view, it should be called a
|
|
||||||
liability card, since it increases your liability to the issuing bank
|
|
||||||
every time you use it.
|
|
||||||
|
|
||||||
In Ledger, credits and debits are given as they are, which means that
|
|
||||||
sometimes you will see a minus sign where you don't expect one. For
|
|
||||||
example, when you get paid, in order to credit your bank account, you
|
|
||||||
need to debit an income account:
|
|
||||||
|
|
||||||
@example
|
|
||||||
9/29 My Employer
|
|
||||||
Assets:Checking $500.00
|
|
||||||
Income:Salary $-500.00
|
|
||||||
@end example
|
|
||||||
|
|
||||||
But wait, you say, why is the Income a negative figure? And when you
|
|
||||||
look at the balance totals for your ledger, you will certainly be
|
|
||||||
surprised to see Expenses as a positive figure, and Income as a
|
|
||||||
negative figure. Isn't that the opposite of how it should look?
|
|
||||||
|
|
||||||
It may take getting used to, but to properly use a general ledger you
|
|
||||||
will need to think in terms of correct debits and credits. Rather
|
|
||||||
than Ledger ``fixing'' the minus signs, let's understand why they are
|
|
||||||
there.
|
|
||||||
|
|
||||||
When you earn money, the money has to come from somewhere. Let's call
|
|
||||||
that somewhere ``society''. In order for society to give you an income,
|
|
||||||
you must take money away from society (debit) in order to put it into
|
|
||||||
your bank (credit). When you then spend that money, it leaves your
|
|
||||||
bank account (debit) and goes back to society (credit). This is why
|
|
||||||
Income will appear negative---it reflects the money you have drawn
|
|
||||||
from society---and why Expenses will be positive---it is the amount
|
|
||||||
you've given back. These credits and debits will always cancel each
|
|
||||||
other out in the end, because you don't have the ability to create new
|
|
||||||
money: It must always come from somewhere, and in the end must always
|
|
||||||
leave. This is the beginning of economy, after which the explanation
|
|
||||||
gets terribly difficult.
|
|
||||||
|
|
||||||
Based on that explanation, here's another way to look at your balance
|
|
||||||
report: Every negative figure means that that account or person or
|
|
||||||
place has less money now than when you started your ledger; and every
|
|
||||||
positive figure means that that account or person or place has more
|
|
||||||
money now that when you started your ledger. Make sense?
|
|
||||||
|
|
||||||
Also, credit cards will have a negative value, because you are
|
|
||||||
spending @emph{from} them (debit) in order pay someone else (credit). They
|
|
||||||
are called credit cards because you are able to instantly credit that
|
|
||||||
other person, by simply waving a card.
|
|
||||||
|
|
||||||
@node Commodities and Currencies, Accounts and Inventories, Credits and Debits, Keeping a ledger
|
|
||||||
@section Commodities and Currencies
|
|
||||||
|
|
||||||
Ledger makes no assumptions about the commodities you use; it only
|
|
||||||
requires that you specify a commodity. The commodity may be any
|
|
||||||
non-numeric string that does not contain a period, comma, forward
|
|
||||||
slash or at-sign. It may appear before or after the amount, although
|
|
||||||
it is assumed that symbols appearing before the amount refer to
|
|
||||||
currencies, while non-joined symbols appearing after the amount refer
|
|
||||||
to commodities. Here are some valid currency and commodity
|
|
||||||
specifiers:
|
|
||||||
|
|
||||||
@example
|
|
||||||
$20.00 ; currency: twenty US dollars
|
|
||||||
USD 20 ; currency: the same
|
|
||||||
40 AAPL ; commodity: 40 shares of Apple stock
|
|
||||||
MD 60 ; currency: 60 Deutsch Mark
|
|
||||||
£50 ; currency: 50 British pounds
|
|
||||||
50e ; currency: 50 Euros (use symbol)
|
|
||||||
@end example
|
|
||||||
|
|
||||||
Ledger will examine the first use of any commodity to determine how
|
|
||||||
that commodity should be printed on reports. It pays attention to
|
|
||||||
whether the name of commodity was separated from the amount, whether
|
|
||||||
it came before or after, the precision used in specifying the amount,
|
|
||||||
whether thousand marks were used, etc. This is done so that printing
|
|
||||||
the commodity looks the same as the way you use it.
|
|
||||||
|
|
||||||
An account may contain multiple commodities, in which case it will
|
|
||||||
have separate totals for each. For example, if your brokerage account
|
|
||||||
contains both cash, gold, and several stock quantities, the balance
|
|
||||||
might look like:
|
|
||||||
|
|
||||||
@example
|
|
||||||
$200.00
|
|
||||||
100.00 AU
|
|
||||||
AAPL 40
|
|
||||||
BORL 100
|
|
||||||
FEQTX 50 Assets:Brokerage
|
|
||||||
@end example
|
|
||||||
|
|
||||||
This balance report shows how much of each commodity is in your
|
|
||||||
brokerage account.
|
|
||||||
|
|
||||||
Sometimes, you will want to know the current street value of your
|
|
||||||
balance, and not the commodity totals. For this to happen, you must
|
|
||||||
specify what the current price is for each commodity. The price can
|
|
||||||
be in any commodity, in which case the balance will be computed in
|
|
||||||
terms of that commodity. The usual way to specify prices is with a
|
|
||||||
file of price settings, which might look like this:
|
|
||||||
|
|
||||||
@example
|
|
||||||
AU=$357.00
|
|
||||||
AAPL=$37
|
|
||||||
BORL=$19
|
|
||||||
FEQTX=$32
|
|
||||||
@end example
|
|
||||||
|
|
||||||
Specify the prices file using the @samp{-p} option:
|
|
||||||
|
|
||||||
@example
|
|
||||||
/home/johnw $ ledger -p prices.db balance brokerage
|
|
||||||
@end example
|
|
||||||
|
|
||||||
Now the balance for your brokerage account will be given in US
|
|
||||||
dollars, since the prices database has specified conversion factors
|
|
||||||
from each commodity into dollars:
|
|
||||||
|
|
||||||
@example
|
|
||||||
$40880.00 Assets:Brokerage
|
|
||||||
@end example
|
|
||||||
|
|
||||||
You can convert from any commodity to any other commodity. Let's say
|
|
||||||
you had $5000 in your checking account, and for whatever reason you
|
|
||||||
wanted to know many ounces of gold that would buy. If gold is
|
|
||||||
currently $357 per ounce, then each dollar is worth 1/357 AU:
|
|
||||||
|
|
||||||
@example
|
|
||||||
/home/johnw $ ledger -p "$=0.00280112 AU" balance checking
|
|
||||||
@end example
|
|
||||||
|
|
||||||
@example
|
|
||||||
14.01 AU Assets:Checking
|
|
||||||
@end example
|
|
||||||
|
|
||||||
$5000 would buy 14 ounces of gold, which becomes the new display
|
|
||||||
commodity since a conversion factor was provided.
|
|
||||||
|
|
||||||
Commodities conversions can also be chained, up to a depth of 10.
|
|
||||||
Here is a sample prices database that uses chaining:
|
|
||||||
|
|
||||||
@example
|
|
||||||
AAPL=$15
|
|
||||||
$=0.00280112 AU
|
|
||||||
AU=300 Euro
|
|
||||||
Euro=MD 0.75
|
|
||||||
@end example
|
|
||||||
|
|
||||||
This is a roundabout way of reporting AAPL shares in their Deutsch
|
|
||||||
Mark equivalent.
|
|
||||||
|
|
||||||
@node Accounts and Inventories, Understanding Equity, Commodities and Currencies, Keeping a ledger
|
|
||||||
@section Accounts and Inventories
|
|
||||||
|
|
||||||
Since Ledger's accounts and commodity system is so flexible, you can
|
|
||||||
have accounts that don't really exist, and use commodities that no one
|
|
||||||
else recognizes. For example, let's say you are buying and selling
|
|
||||||
various items in EverQuest, and want to keep track of them using a
|
|
||||||
ledger. Just add items of whatever quantity you wish into your
|
|
||||||
EverQuest account:
|
|
||||||
|
|
||||||
@example
|
|
||||||
9/29 Get some stuff at the Inn
|
|
||||||
Places:Black's Tavern -3 Apples
|
|
||||||
Places:Black's Tavern -5 Steaks
|
|
||||||
EverQuest:Inventory
|
|
||||||
@end example
|
|
||||||
|
|
||||||
Now your EverQuest:Inventory has 3 apples and 5 steaks in it. The
|
|
||||||
amounts are negative, because you are taking @emph{from} Black's Tavern in
|
|
||||||
order to credit your Inventory account. Note that you don't have to
|
|
||||||
use ``Places:Black's Tavern'' as the source account. You could use
|
|
||||||
``EverQuest:System'' to represent the fact that you acquired them
|
|
||||||
online. The only purpose for choosing one kind of source account over
|
|
||||||
another is for generate more informative reports later on. The more
|
|
||||||
you know, the better analysis you can perform.
|
|
||||||
|
|
||||||
If you later sell some of these items to another player, the entry
|
|
||||||
would look like:
|
|
||||||
|
|
||||||
@example
|
|
||||||
10/2 Strum Brightblade
|
|
||||||
EverQuest:Inventory -2 Steaks
|
|
||||||
EverQuest:Inventory 15 Gold
|
|
||||||
@end example
|
|
||||||
|
|
||||||
Now you've turned 2 steaks into 15 gold, courtesy of your customer,
|
|
||||||
Strum Brightblade.
|
|
||||||
|
|
||||||
@node Understanding Equity, , Accounts and Inventories, Keeping a ledger
|
|
||||||
@section Understanding Equity
|
|
||||||
|
|
||||||
The most confusing entry in any ledger will be your equity account---
|
|
||||||
because starting balances can't come out of nowhere.
|
|
||||||
|
|
||||||
When you first start your ledger, you will likely already have money
|
|
||||||
in some of your accounts. Let's say there's $100 in your checking
|
|
||||||
account; then add an entry to your ledger to reflect this amount.
|
|
||||||
Where will money come from? The answer: your equity.
|
|
||||||
|
|
||||||
@example
|
|
||||||
10/2 Opening Balance
|
|
||||||
Assets:Checking $100.00
|
|
||||||
Equity:Opening Balances $-100.00
|
|
||||||
@end example
|
|
||||||
|
|
||||||
But what is equity? You may have heard of equity when people talked
|
|
||||||
about house mortgages, as ``the part of the house that you own''.
|
|
||||||
Basically, equity is like the value of something. If you own a car
|
|
||||||
worth $5000, then you have $5000 in equity in that car. In order to
|
|
||||||
turn that car (a commodity) into a cash flow, or a credit to your bank
|
|
||||||
account, you will have to debit the equity by selling it.
|
|
||||||
|
|
||||||
When you start a ledger, you are probably already worth something.
|
|
||||||
Your net worth is your current equity. By transferring the money in
|
|
||||||
the ledger from your equity to your bank accounts, you are crediting
|
|
||||||
the ledger account based on your prior equity value. That is why,
|
|
||||||
when you look at the balance report, you will see a large negative
|
|
||||||
number for Equity that never changes: Because that is what you were
|
|
||||||
worth (what you debited from yourself in order to start the ledger)
|
|
||||||
before the money started moving around. If the total positive value
|
|
||||||
of your assets is greater than the absolute value of your starting
|
|
||||||
equity, it means you are making money.
|
|
||||||
|
|
||||||
Clear as mud? Keep thinking about it. Until you figure it out, put
|
|
||||||
``-- -Equity'' at the end of your balance command, to remove the
|
|
||||||
confusing figure from the totals.
|
|
||||||
|
|
||||||
@node Using the Ledger Tool, An easy way to add new entries, Keeping a ledger, Top
|
|
||||||
@chapter Using the Ledger Tool
|
|
||||||
|
|
||||||
Now that you have an orderly and well-organized general ledger, it's
|
|
||||||
time to start generating some orderly and well-organized reports.
|
|
||||||
This is where the Ledger tool comes in. With it, you can balance your
|
|
||||||
checkbook, see where your money is going, tell whether you've made a
|
|
||||||
profit this year, and even compute the present day value of your
|
|
||||||
retirement accounts. And all with the simplest of interfaces: the
|
|
||||||
command-line.
|
|
||||||
|
|
||||||
The most often used command will be the ``balance'' command:
|
|
||||||
|
|
||||||
@example
|
|
||||||
/home/johnw $ export LEDGER=/home/johnw/doc/ledger.dat
|
|
||||||
/home/johnw $ ledger balance
|
|
||||||
@end example
|
|
||||||
|
|
||||||
Here I've set my Ledger environment variable to point to where my
|
|
||||||
ledger file is hiding. Thereafter, I needn't specify it again.
|
|
||||||
|
|
||||||
The balance command prints out the summarized balances of all my
|
|
||||||
top-level accounts, excluding sub-accounts. In order to see the
|
|
||||||
balances for a specific account, just specify a regular expression
|
|
||||||
after the balance command:
|
|
||||||
|
|
||||||
@example
|
|
||||||
/home/johnw $ ledger balance expenses:food
|
|
||||||
@end example
|
|
||||||
|
|
||||||
This will show all the money that's been spent on food, since the
|
|
||||||
beginning of the ledger. For food spending just this month
|
|
||||||
(September), use:
|
|
||||||
|
|
||||||
@example
|
|
||||||
/home/johnw $ ledger -d sep balance expenses:food
|
|
||||||
@end example
|
|
||||||
|
|
||||||
Or maybe I want to see all of my assets, in which case the -s (show
|
|
||||||
sub-accounts) option comes in handy:
|
|
||||||
|
|
||||||
@example
|
|
||||||
/home/johnw $ ledger balance -s
|
|
||||||
@end example
|
|
||||||
|
|
||||||
To exclude a particular account, use a regular expression with a
|
|
||||||
leading minus sign. The following will show all expenses, but without
|
|
||||||
food spending:
|
|
||||||
|
|
||||||
@example
|
|
||||||
/home/johnw $ ledger balance expenses -food
|
|
||||||
@end example
|
|
||||||
|
|
||||||
If you want to show all accounts but for one account, remember to use
|
|
||||||
``--'' to separate the exclusion pattern from the options list:
|
|
||||||
|
|
||||||
@example
|
|
||||||
/home/johnw $ ledger balance -- -equity
|
|
||||||
@end example
|
|
||||||
|
|
||||||
@menu
|
|
||||||
* Virtual transactions::
|
|
||||||
* Automated transactions::
|
|
||||||
@end menu
|
|
||||||
|
|
||||||
@node Virtual transactions, Automated transactions, Using the Ledger Tool, Using the Ledger Tool
|
|
||||||
@section Virtual transactions
|
|
||||||
|
|
||||||
A virtual transaction is when you, in your mind, see money as moving
|
|
||||||
to a certain place, when in reality that money has not moved at all.
|
|
||||||
There are several scenarios in which this type of tracking comes in
|
|
||||||
handy, and each of them will be discussed in detail.
|
|
||||||
|
|
||||||
To enter a virtual transaction, surround the account name in
|
|
||||||
parentheses. This form of usage does not need to balance. However,
|
|
||||||
if you want to ensure the virtual transaction balances with other
|
|
||||||
virtual transactions in the same entry, use square brackets. For
|
|
||||||
example:
|
|
||||||
|
|
||||||
@example
|
|
||||||
10/2 Paycheck
|
|
||||||
Assets:Checking $1000.00
|
|
||||||
Income:Salary $-1000.00
|
|
||||||
(Debt:Alimony) $200.00
|
|
||||||
@end example
|
|
||||||
|
|
||||||
In this example, after receiving a paycheck an alimony debt is
|
|
||||||
increased---even though no money has moved around yet.
|
|
||||||
|
|
||||||
@example
|
|
||||||
10/2 Paycheck
|
|
||||||
Assets:Checking $1000.00
|
|
||||||
Income:Salary $-1000.00
|
|
||||||
[Savings:Trip] $200.00
|
|
||||||
[Assets:Checking] $-200.00
|
|
||||||
@end example
|
|
||||||
|
|
||||||
In this example, $200 has been deducted from checking toward savings
|
|
||||||
for a trip. It will appear as though the money has been moved from
|
|
||||||
the account into ``Savings:Trip'', although no money has actually moved
|
|
||||||
anywhere.
|
|
||||||
|
|
||||||
When balances are displayed, virtual transactions will be factored in.
|
|
||||||
To view balances without any virtual balances factored in, using the
|
|
||||||
``-R'' flag, for ``Reality''.
|
|
||||||
|
|
||||||
@menu
|
|
||||||
* Saving for a Special Occasion::
|
|
||||||
* Keeping a Budget::
|
|
||||||
* Tracking Allocated Funds::
|
|
||||||
@end menu
|
|
||||||
|
|
||||||
@node Saving for a Special Occasion, Keeping a Budget, Virtual transactions, Virtual transactions
|
|
||||||
@subsection Saving for a Special Occasion
|
|
||||||
|
|
||||||
@node Keeping a Budget, Tracking Allocated Funds, Saving for a Special Occasion, Virtual transactions
|
|
||||||
@subsection Keeping a Budget
|
|
||||||
|
|
||||||
@node Tracking Allocated Funds, , Keeping a Budget, Virtual transactions
|
|
||||||
@subsection Tracking Allocated Funds
|
|
||||||
|
|
||||||
@node Automated transactions, , Virtual transactions, Using the Ledger Tool
|
|
||||||
@section Automated transactions
|
|
||||||
|
|
||||||
@menu
|
|
||||||
* Computing Bahá'í Huqúqu'lláh::
|
|
||||||
@end menu
|
|
||||||
|
|
||||||
@node Computing Bahá'í Huqúqu'lláh, , Automated transactions, Automated transactions
|
|
||||||
@subsection Computing Bahá'í Huqúqu'lláh
|
|
||||||
|
|
||||||
As a Bahá'í, I need to compute Huqúqu'lláh on some of my assets. The
|
|
||||||
exact details of this matter are rather complex, so if you have any
|
|
||||||
interest, I encourage you to do research on the Web.
|
|
||||||
|
|
||||||
For any fellow Bahá'ís out there who want to track Huqúqu'lláh, the
|
|
||||||
Ledger tool makes this extraordinarily easy. Just too easy, in fact.
|
|
||||||
Here's all you have to do: If an expense is exempt from Huqúqu'lláh---
|
|
||||||
a ``necessary'' expense---put an exclamation mark before the account
|
|
||||||
name:
|
|
||||||
|
|
||||||
@example
|
|
||||||
2002.12.31 Rent
|
|
||||||
!Expenses:Rent $450.00
|
|
||||||
Assets:Checking $-450.00
|
|
||||||
@end example
|
|
||||||
|
|
||||||
Even easier than that, simply put a list of regular expressions that
|
|
||||||
match the categories you consider exempt in a file called @samp{.huquq},
|
|
||||||
and the special marking will be done for you. Here is the file I use:
|
|
||||||
|
|
||||||
@example
|
|
||||||
^Income:
|
|
||||||
^Retirement:
|
|
||||||
^Expenses:Rent
|
|
||||||
^Expenses:Taxes
|
|
||||||
@end example
|
|
||||||
|
|
||||||
When you're ready to pay Huqúqu'lláh, just write the check to the
|
|
||||||
account ``Huququ'llah'' (no accents, one apostrophe):
|
|
||||||
|
|
||||||
@example
|
|
||||||
2003.01.20 * (101) Baha'i Huququ'llah Trust
|
|
||||||
Huququ'llah $1,000.00
|
|
||||||
Assets:Checking $-1,000.00
|
|
||||||
@end example
|
|
||||||
|
|
||||||
That's it. To see how much Huqúq is currently owed based on your
|
|
||||||
ledger data, type:
|
|
||||||
|
|
||||||
@example
|
|
||||||
/home/johnw $ ledger balance ^huquq
|
|
||||||
@end example
|
|
||||||
|
|
||||||
@node An easy way to add new entries, Using Emacs to Keep Your Ledger, Using the Ledger Tool, Top
|
|
||||||
@chapter An easy way to add new entries
|
|
||||||
|
|
||||||
The three most laborious tasks of keeping a ledger are: adding new
|
|
||||||
entries, reconciling accounts, and generating reports. To address the
|
|
||||||
first of these, there is a sub-command to ledger called ``entry''. It
|
|
||||||
works on the principle that 80% of all transactions are variants of
|
|
||||||
earlier transactions. Here's how it works:
|
|
||||||
|
|
||||||
Let's say you have an old transaction of the following form:
|
|
||||||
|
|
||||||
@example
|
|
||||||
2004/03/15 * Viva Italiano
|
|
||||||
Expenses:Food $12.45
|
|
||||||
Expenses:Tips $2.55
|
|
||||||
Liabilities:MasterCard $-15.00
|
|
||||||
@end example
|
|
||||||
|
|
||||||
Now it's 2004/4/9, and you've just eating at Viva Italiano again. The
|
|
||||||
exact amounts are different, but the overall form is the same. With
|
|
||||||
the ``entry'' command you can type:
|
|
||||||
|
|
||||||
@example
|
|
||||||
ledger entry 2004/4/9 viva food 11.00 tips 2.50
|
|
||||||
@end example
|
|
||||||
|
|
||||||
This will produce the following output:
|
|
||||||
|
|
||||||
@example
|
|
||||||
2004/04/09 Viva Italiano
|
|
||||||
Expenses:Food $11.00
|
|
||||||
Expenses:Tips $2.50
|
|
||||||
Liabilities:MasterCard $-13.50
|
|
||||||
@end example
|
|
||||||
|
|
||||||
This works by finding a transaction that matches the regexp ``viva'',
|
|
||||||
and then assuming that any accounts or amounts you specify will be the
|
|
||||||
same as that earlier transaction. If Ledger does not succeed in
|
|
||||||
generating a new entry for you, it will print an error and set the
|
|
||||||
exit code to 1.
|
|
||||||
|
|
||||||
There is a shell script in the distribution called ``entry'', which
|
|
||||||
simplifies the task of adding a new entry to your ledger, and then
|
|
||||||
launches @samp{vi} to let you confirm that the entry looks appropriate.
|
|
||||||
|
|
||||||
@node Using Emacs to Keep Your Ledger, Using GnuCash to Keep Your Ledger, An easy way to add new entries, Top
|
|
||||||
@chapter Using Emacs to Keep Your Ledger
|
|
||||||
|
|
||||||
In the Ledger tarball is an Emacs module, @samp{ledger.el}. This module
|
|
||||||
makes the process of keeping a text ledger much easier for Emacs
|
|
||||||
users. I recommend putting this at the top of your ledger file:
|
|
||||||
|
|
||||||
@example
|
|
||||||
; -*-ledger-*-
|
|
||||||
@end example
|
|
||||||
|
|
||||||
And this in your @samp{.emacs} file, after copying @samp{ledger.el} to your
|
|
||||||
site-lisp directory:
|
|
||||||
|
|
||||||
@example
|
|
||||||
(load "ledger")
|
|
||||||
@end example
|
|
||||||
|
|
||||||
Now when you edit your ledger file, it will be in @samp{ledger-mode}.
|
|
||||||
@samp{ledger-mode} adds the following commands:
|
|
||||||
|
|
||||||
@table @strong
|
|
||||||
@item C-c C-a
|
|
||||||
For quickly adding new entries based on the form of older ones
|
|
||||||
(see previous section).
|
|
||||||
|
|
||||||
@item C-c C-c
|
|
||||||
Toggles the ``cleared'' flag of the transaction under point.
|
|
||||||
|
|
||||||
@item C-c C-r
|
|
||||||
Reconciles an account by displaying the transactions in another
|
|
||||||
buffer, where simply hitting the spacebar will toggle the cleared
|
|
||||||
flag of the transaction in the ledger. It also displays the current
|
|
||||||
cleared balance for the account in the modeline.
|
|
||||||
|
|
||||||
@end table
|
|
||||||
|
|
||||||
@node Using GnuCash to Keep Your Ledger, , Using Emacs to Keep Your Ledger, Top
|
|
||||||
@chapter Using GnuCash to Keep Your Ledger
|
|
||||||
|
|
||||||
The Ledger tool is fast and simple, but it gives you no special
|
|
||||||
method of actually editing the ledger. It assumes you know how to use
|
|
||||||
a text editor, and like doing so. Perhaps an Emacs mode will appear
|
|
||||||
someday soon to make editing Ledger's data files much easier.
|
|
||||||
|
|
||||||
Until then, you are free to use GnuCash to maintain your ledger, and
|
|
||||||
the Ledger program for querying and reporting on the contents
|
|
||||||
of that ledger. It takes a little longer to parse the XML data format
|
|
||||||
that GnuCash uses, but the end result is identical.
|
|
||||||
|
|
||||||
Then again, why would anyone use a Gnome-centric, 35 megabyte behemoth
|
|
||||||
to edit their data, and a 65 kilobyte binary to query it...
|
|
||||||
|
|
||||||
|
|
||||||
@c Page published by Emacs Muse ends here
|
|
||||||
@contents
|
|
||||||
@bye
|
|
||||||
238
parse.cc
238
parse.cc
|
|
@ -5,6 +5,8 @@
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
|
|
||||||
|
#define TIMELOG_SUPPORT 1
|
||||||
|
|
||||||
namespace ledger {
|
namespace ledger {
|
||||||
|
|
||||||
static inline char * skip_ws(char * ptr)
|
static inline char * skip_ws(char * ptr)
|
||||||
|
|
@ -104,13 +106,11 @@ void parse_price_setting(const std::string& setting)
|
||||||
*p++ = '\0';
|
*p++ = '\0';
|
||||||
|
|
||||||
commodity * comm = NULL;
|
commodity * comm = NULL;
|
||||||
|
|
||||||
commodities_map_iterator item = main_ledger->commodities.find(c);
|
commodities_map_iterator item = main_ledger->commodities.find(c);
|
||||||
if (item == main_ledger->commodities.end()) {
|
if (item == main_ledger->commodities.end())
|
||||||
comm = new commodity(c);
|
comm = new commodity(c);
|
||||||
} else {
|
else
|
||||||
comm = (*item).second;
|
comm = (*item).second;
|
||||||
}
|
|
||||||
|
|
||||||
assert(comm);
|
assert(comm);
|
||||||
comm->price = create_amount(p);
|
comm->price = create_amount(p);
|
||||||
|
|
@ -119,26 +119,20 @@ void parse_price_setting(const std::string& setting)
|
||||||
|
|
||||||
#define MAX_LINE 1024
|
#define MAX_LINE 1024
|
||||||
|
|
||||||
int linenum;
|
int linenum;
|
||||||
|
static bool do_compute;
|
||||||
|
static std::string account_prefix;
|
||||||
|
|
||||||
static bool do_compute;
|
transaction * parse_transaction_text(char * line, book * ledger)
|
||||||
|
|
||||||
transaction * parse_transaction(std::istream& in, book * ledger)
|
|
||||||
{
|
{
|
||||||
transaction * xact = new transaction();
|
transaction * xact = new transaction();
|
||||||
|
|
||||||
static char line[MAX_LINE + 1];
|
|
||||||
in.getline(line, MAX_LINE);
|
|
||||||
linenum++;
|
|
||||||
|
|
||||||
char * p = line;
|
|
||||||
p = skip_ws(p);
|
|
||||||
|
|
||||||
// The call to `next_element' will skip past the account name,
|
// The call to `next_element' will skip past the account name,
|
||||||
// and return a pointer to the beginning of the amount. Once
|
// and return a pointer to the beginning of the amount. Once
|
||||||
// we know where the amount is, we can strip off any
|
// we know where the amount is, we can strip off any
|
||||||
// transaction note, and parse it.
|
// transaction note, and parse it.
|
||||||
|
|
||||||
|
char * p = skip_ws(line);
|
||||||
char * cost_str = next_element(p, true);
|
char * cost_str = next_element(p, true);
|
||||||
char * note_str;
|
char * note_str;
|
||||||
|
|
||||||
|
|
@ -181,7 +175,8 @@ transaction * parse_transaction(std::istream& in, book * ledger)
|
||||||
*e = '\0';
|
*e = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
xact->acct = ledger->find_account(p);
|
std::string name = account_prefix + p;
|
||||||
|
xact->acct = ledger->find_account(name.c_str());
|
||||||
|
|
||||||
if (do_compute && xact->cost)
|
if (do_compute && xact->cost)
|
||||||
xact->acct->balance.credit(xact->cost);
|
xact->acct->balance.credit(xact->cost);
|
||||||
|
|
@ -189,6 +184,15 @@ transaction * parse_transaction(std::istream& in, book * ledger)
|
||||||
return xact;
|
return xact;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transaction * parse_transaction(std::istream& in, book * ledger)
|
||||||
|
{
|
||||||
|
static char line[MAX_LINE + 1];
|
||||||
|
in.getline(line, MAX_LINE);
|
||||||
|
linenum++;
|
||||||
|
|
||||||
|
return parse_transaction_text(line, ledger);
|
||||||
|
}
|
||||||
|
|
||||||
entry * parse_entry(std::istream& in, book * ledger)
|
entry * parse_entry(std::istream& in, book * ledger)
|
||||||
{
|
{
|
||||||
entry * curr = new entry(ledger);
|
entry * curr = new entry(ledger);
|
||||||
|
|
@ -247,7 +251,7 @@ void parse_automated_transactions(std::istream& in, book * ledger)
|
||||||
{
|
{
|
||||||
static char line[MAX_LINE + 1];
|
static char line[MAX_LINE + 1];
|
||||||
|
|
||||||
regexps_map * masks = NULL;
|
regexps_list * masks = NULL;
|
||||||
|
|
||||||
while (! in.eof() && in.peek() == '=') {
|
while (! in.eof() && in.peek() == '=') {
|
||||||
in.getline(line, MAX_LINE);
|
in.getline(line, MAX_LINE);
|
||||||
|
|
@ -257,7 +261,7 @@ void parse_automated_transactions(std::istream& in, book * ledger)
|
||||||
p = skip_ws(p);
|
p = skip_ws(p);
|
||||||
|
|
||||||
if (! masks)
|
if (! masks)
|
||||||
masks = new regexps_map;
|
masks = new regexps_list;
|
||||||
masks->push_back(mask(p));
|
masks->push_back(mask(p));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -291,22 +295,32 @@ void parse_automated_transactions(std::istream& in, book * ledger)
|
||||||
// Ledger parser
|
// Ledger parser
|
||||||
//
|
//
|
||||||
|
|
||||||
book * parse_ledger(std::istream& in, regexps_map& regexps,
|
#ifdef TIMELOG_SUPPORT
|
||||||
bool compute_balances)
|
static std::time_t time_in;
|
||||||
|
static account * last_account;
|
||||||
|
static std::string last_desc;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int parse_ledger(book * ledger, std::istream& in,
|
||||||
|
regexps_list& regexps, bool compute_balances,
|
||||||
|
const char * acct_prefix)
|
||||||
{
|
{
|
||||||
static char line[MAX_LINE + 1];
|
static char line[MAX_LINE + 1];
|
||||||
char c;
|
char c;
|
||||||
|
int count = 0;
|
||||||
|
std::string old_account_prefix = account_prefix;
|
||||||
|
|
||||||
book * ledger = new book;
|
linenum = 1;
|
||||||
|
do_compute = compute_balances;
|
||||||
main_ledger = ledger;
|
if (acct_prefix) {
|
||||||
do_compute = compute_balances;
|
account_prefix += acct_prefix;
|
||||||
linenum = 0;
|
account_prefix += ":";
|
||||||
|
}
|
||||||
|
|
||||||
while (! in.eof()) {
|
while (! in.eof()) {
|
||||||
switch (in.peek()) {
|
switch (in.peek()) {
|
||||||
case -1: // end of file
|
case -1: // end of file
|
||||||
return ledger;
|
goto done;
|
||||||
|
|
||||||
case '\n':
|
case '\n':
|
||||||
linenum++;
|
linenum++;
|
||||||
|
|
@ -314,11 +328,99 @@ book * parse_ledger(std::istream& in, regexps_map& regexps,
|
||||||
in.get(c);
|
in.get(c);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
#ifdef TIMELOG_SUPPORT
|
||||||
|
case 'i':
|
||||||
|
case 'I': {
|
||||||
|
std::string date, time;
|
||||||
|
|
||||||
|
in >> c;
|
||||||
|
in >> date;
|
||||||
|
in >> time;
|
||||||
|
date += " ";
|
||||||
|
date += time;
|
||||||
|
|
||||||
|
in.getline(line, MAX_LINE);
|
||||||
|
linenum++;
|
||||||
|
|
||||||
|
char * p = skip_ws(line);
|
||||||
|
char * n = next_element(p, true);
|
||||||
|
last_desc = n ? n : "";
|
||||||
|
|
||||||
|
static struct std::tm when;
|
||||||
|
if (strptime(date.c_str(), "%Y/%m/%d %H:%M:%S", &when)) {
|
||||||
|
time_in = std::mktime(&when);
|
||||||
|
last_account = ledger->find_account(p);
|
||||||
|
} else {
|
||||||
|
std::cerr << "Error, line " << (linenum - 1)
|
||||||
|
<< ": Cannot parse timelog entry date."
|
||||||
|
<< std::endl;
|
||||||
|
last_account = NULL;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'o':
|
||||||
|
case 'O':
|
||||||
|
if (last_account) {
|
||||||
|
std::string date, time;
|
||||||
|
|
||||||
|
in >> c;
|
||||||
|
in >> date;
|
||||||
|
in >> time;
|
||||||
|
date += " ";
|
||||||
|
date += time;
|
||||||
|
|
||||||
|
static struct std::tm when;
|
||||||
|
if (strptime(date.c_str(), "%Y/%m/%d %H:%M:%S", &when)) {
|
||||||
|
entry * curr = new entry(ledger);
|
||||||
|
|
||||||
|
curr->date = std::mktime(&when);
|
||||||
|
|
||||||
|
double diff = (curr->date - time_in) / 60.0 / 60.0;
|
||||||
|
char buf[128];
|
||||||
|
std::sprintf(buf, "%fh", diff);
|
||||||
|
|
||||||
|
curr->cleared = true;
|
||||||
|
curr->code = "";
|
||||||
|
curr->desc = last_desc;
|
||||||
|
|
||||||
|
std::string xact_line = "(";
|
||||||
|
xact_line += last_account->as_str();
|
||||||
|
xact_line += ") ";
|
||||||
|
xact_line += buf;
|
||||||
|
|
||||||
|
std::strcpy(buf, xact_line.c_str());
|
||||||
|
|
||||||
|
if (transaction * xact = parse_transaction_text(buf, ledger)) {
|
||||||
|
curr->xacts.push_back(xact);
|
||||||
|
|
||||||
|
// Make sure numbers are reported only to 1 decimal place.
|
||||||
|
commodity * cmdty = xact->cost->commdty();
|
||||||
|
cmdty->precision = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ledger->entries.push_back(curr);
|
||||||
|
count++;
|
||||||
|
} else {
|
||||||
|
std::cerr << "Error, line " << (linenum - 1)
|
||||||
|
<< ": Cannot parse timelog entry date."
|
||||||
|
<< std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
last_account = NULL;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#endif // TIMELOG_SUPPORT
|
||||||
|
|
||||||
case 'Y': // set the current year
|
case 'Y': // set the current year
|
||||||
in >> c;
|
in >> c;
|
||||||
in >> ledger->current_year;
|
in >> ledger->current_year;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
#ifdef TIMELOG_SUPPORT
|
||||||
|
case 'h':
|
||||||
|
case 'b':
|
||||||
|
#endif
|
||||||
case ';': // a comment line
|
case ';': // a comment line
|
||||||
in.getline(line, MAX_LINE);
|
in.getline(line, MAX_LINE);
|
||||||
linenum++;
|
linenum++;
|
||||||
|
|
@ -339,13 +441,89 @@ book * parse_ledger(std::istream& in, regexps_map& regexps,
|
||||||
do_compute = compute_balances;
|
do_compute = compute_balances;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case '!': // directive
|
||||||
|
in >> line;
|
||||||
|
if (std::string(line) == "!include") {
|
||||||
|
std::string path;
|
||||||
|
bool has_prefix = false;
|
||||||
|
|
||||||
|
in >> path;
|
||||||
|
|
||||||
|
if (in.peek() == ' ') {
|
||||||
|
has_prefix = true;
|
||||||
|
in.getline(line, MAX_LINE);
|
||||||
|
}
|
||||||
|
|
||||||
|
int curr_linenum = linenum;
|
||||||
|
count += parse_ledger_file(ledger, path, regexps, compute_balances,
|
||||||
|
has_prefix ? skip_ws(line) : NULL);
|
||||||
|
linenum = curr_linenum;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if (entry * ent = parse_entry(in, ledger))
|
if (entry * ent = parse_entry(in, ledger)) {
|
||||||
ledger->entries.push_back(ent);
|
ledger->entries.push_back(ent);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ledger;
|
|
||||||
|
done:
|
||||||
|
account_prefix = old_account_prefix;
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
int parse_ledger_file(book * ledger, const std::string& file,
|
||||||
|
regexps_list& regexps, bool compute_balances,
|
||||||
|
const char * acct_prefix)
|
||||||
|
{
|
||||||
|
std::ifstream stream(file.c_str());
|
||||||
|
|
||||||
|
// Parse the ledger
|
||||||
|
|
||||||
|
#ifdef READ_GNUCASH
|
||||||
|
char buf[32];
|
||||||
|
stream.get(buf, 31);
|
||||||
|
stream.seekg(0);
|
||||||
|
|
||||||
|
if (std::strncmp(buf, "<?xml version=\"1.0\"?>", 21) == 0)
|
||||||
|
return parse_gnucash(ledger, stream, compute_balances);
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
return parse_ledger(ledger, stream, regexps, compute_balances,
|
||||||
|
acct_prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Read other kinds of data from files
|
||||||
|
//
|
||||||
|
|
||||||
|
void read_regexps(const std::string& path, regexps_list& regexps)
|
||||||
|
{
|
||||||
|
std::ifstream file(path.c_str());
|
||||||
|
|
||||||
|
while (! file.eof()) {
|
||||||
|
char buf[80];
|
||||||
|
file.getline(buf, 79);
|
||||||
|
if (*buf && ! std::isspace(*buf))
|
||||||
|
regexps.push_back(mask(buf));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void read_prices(const std::string& path)
|
||||||
|
{
|
||||||
|
std::ifstream file(path.c_str());
|
||||||
|
|
||||||
|
while (! file.eof()) {
|
||||||
|
char buf[80];
|
||||||
|
file.getline(buf, 79);
|
||||||
|
if (*buf && ! std::isspace(*buf))
|
||||||
|
parse_price_setting(buf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ledger
|
} // namespace ledger
|
||||||
|
|
|
||||||
595
reports.cc
595
reports.cc
|
|
@ -1,13 +1,15 @@
|
||||||
#include "ledger.h"
|
#include "ledger.h"
|
||||||
|
|
||||||
#define LEDGER_VERSION "1.3"
|
#define LEDGER_VERSION "1.6"
|
||||||
|
|
||||||
#include <fstream>
|
#include <cstring>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
namespace ledger {
|
namespace ledger {
|
||||||
|
|
||||||
static bool cleared_only = false;
|
static bool cleared_only = false;
|
||||||
|
static bool uncleared_only = false;
|
||||||
|
static bool cost_basis = false;
|
||||||
static bool show_virtual = true;
|
static bool show_virtual = true;
|
||||||
static bool get_quotes = false;
|
static bool get_quotes = false;
|
||||||
static bool show_children = false;
|
static bool show_children = false;
|
||||||
|
|
@ -15,6 +17,12 @@ static bool show_sorted = false;
|
||||||
static bool show_empty = false;
|
static bool show_empty = false;
|
||||||
static bool show_subtotals = true;
|
static bool show_subtotals = true;
|
||||||
static bool full_names = false;
|
static bool full_names = false;
|
||||||
|
static bool print_monthly = false;
|
||||||
|
static bool gnuplot_safe = false;
|
||||||
|
|
||||||
|
static amount * lower_limit = NULL;
|
||||||
|
|
||||||
|
static mask * negonly_regexp = NULL;
|
||||||
|
|
||||||
static std::time_t begin_date;
|
static std::time_t begin_date;
|
||||||
static bool have_beginning = false;
|
static bool have_beginning = false;
|
||||||
|
|
@ -65,39 +73,156 @@ static bool matches_date_range(entry * ent)
|
||||||
// Balance reporting code
|
// Balance reporting code
|
||||||
//
|
//
|
||||||
|
|
||||||
static void display_total(std::ostream& out, totals& balance,
|
static bool satisfies_limit(totals& balance)
|
||||||
account * acct, bool top_level,
|
|
||||||
int * headlines)
|
|
||||||
{
|
{
|
||||||
bool displayed = false;
|
bool satisfies = true;
|
||||||
|
bool invert = false;
|
||||||
|
|
||||||
if (acct->checked == 1 &&
|
assert(lower_limit);
|
||||||
(show_empty || ! acct->balance.is_zero())) {
|
|
||||||
displayed = true;
|
|
||||||
|
|
||||||
acct->balance.print(out, 20);
|
if (balance.is_negative())
|
||||||
if (show_subtotals && top_level)
|
invert = true;
|
||||||
balance.credit(acct->balance);
|
else
|
||||||
|
lower_limit->negate();
|
||||||
|
|
||||||
if (acct->parent && ! full_names && ! top_level) {
|
balance.credit(lower_limit);
|
||||||
for (const account * a = acct; a; a = a->parent)
|
if (balance.is_negative())
|
||||||
out << " ";
|
satisfies = invert;
|
||||||
out << acct->name << std::endl;
|
else
|
||||||
} else {
|
satisfies = ! invert;
|
||||||
out << " " << acct->as_str() << std::endl;
|
|
||||||
(*headlines)++;
|
lower_limit->negate();
|
||||||
|
balance.credit(lower_limit);
|
||||||
|
|
||||||
|
if (invert)
|
||||||
|
lower_limit->negate();
|
||||||
|
|
||||||
|
return satisfies;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool satisfies_limit(amount * balance)
|
||||||
|
{
|
||||||
|
bool satisfies = true;
|
||||||
|
bool invert = false;
|
||||||
|
|
||||||
|
assert(lower_limit);
|
||||||
|
|
||||||
|
if (balance->is_negative())
|
||||||
|
invert = true;
|
||||||
|
else
|
||||||
|
lower_limit->negate();
|
||||||
|
|
||||||
|
balance->credit(lower_limit);
|
||||||
|
if (balance->is_negative())
|
||||||
|
satisfies = invert;
|
||||||
|
else
|
||||||
|
satisfies = ! invert;
|
||||||
|
|
||||||
|
lower_limit->negate();
|
||||||
|
balance->credit(lower_limit);
|
||||||
|
|
||||||
|
if (invert)
|
||||||
|
lower_limit->negate();
|
||||||
|
|
||||||
|
return satisfies;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void adjust_total(account * acct)
|
||||||
|
{
|
||||||
|
for (accounts_map_iterator i = acct->children.begin();
|
||||||
|
i != acct->children.end();
|
||||||
|
i++)
|
||||||
|
adjust_total((*i).second);
|
||||||
|
|
||||||
|
if (acct->checked == 1) {
|
||||||
|
if (! show_empty && acct->balance.is_zero())
|
||||||
|
acct->checked = 2;
|
||||||
|
else if (lower_limit && ! satisfies_limit(acct->balance))
|
||||||
|
acct->checked = 2;
|
||||||
|
else if (negonly_regexp && negonly_regexp->match(acct->as_str()) &&
|
||||||
|
! acct->balance.is_negative())
|
||||||
|
acct->checked = 2;
|
||||||
|
|
||||||
|
if (acct->checked == 2) {
|
||||||
|
acct->balance.negate();
|
||||||
|
for (account * a = acct->parent; a; a = a->parent)
|
||||||
|
a->balance.credit(acct->balance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int acct_visible_children(account * acct)
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
for (accounts_map_iterator i = acct->children.begin();
|
||||||
|
i != acct->children.end();
|
||||||
|
i++) {
|
||||||
|
if ((*i).second->checked == 1) {
|
||||||
|
if ((*i).second->children.size() == 0)
|
||||||
|
count++;
|
||||||
|
else
|
||||||
|
count += acct_visible_children((*i).second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void display_total(std::ostream& out, totals& balance,
|
||||||
|
account * acct, int level, int * headlines)
|
||||||
|
{
|
||||||
|
// If the number of visible children is exactly one, do not print
|
||||||
|
// the parent account, but just the one child (whose name will
|
||||||
|
// output with sufficiently qualification).
|
||||||
|
|
||||||
|
if (acct->checked == 1 && acct_visible_children(acct) != 1) {
|
||||||
|
if (acct->balance.is_zero()) {
|
||||||
|
out.width(20);
|
||||||
|
out << " ";
|
||||||
|
} else {
|
||||||
|
acct->balance.print(out, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (level == 0 || full_names || ! show_subtotals) {
|
||||||
|
if (show_subtotals) {
|
||||||
|
balance.credit(acct->balance);
|
||||||
|
(*headlines)++;
|
||||||
|
}
|
||||||
|
|
||||||
|
out << " " << acct->as_str() << std::endl;
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < level + 1; i++)
|
||||||
|
out << " ";
|
||||||
|
|
||||||
|
assert(acct->parent);
|
||||||
|
if (acct_visible_children(acct->parent) == 1) {
|
||||||
|
/* If the account has no other siblings, instead of printing:
|
||||||
|
Parent
|
||||||
|
Child
|
||||||
|
print:
|
||||||
|
Parent:Child */
|
||||||
|
const account * parent;
|
||||||
|
for (parent = acct->parent;
|
||||||
|
parent->parent && acct_visible_children(parent->parent) == 1;
|
||||||
|
parent = parent->parent) {}
|
||||||
|
|
||||||
|
out << acct->as_str(parent) << std::endl;
|
||||||
|
} else {
|
||||||
|
out << acct->name << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
level++;
|
||||||
|
}
|
||||||
|
|
||||||
// Display balances for all child accounts
|
// Display balances for all child accounts
|
||||||
|
|
||||||
for (accounts_map_iterator i = acct->children.begin();
|
for (accounts_map_iterator i = acct->children.begin();
|
||||||
i != acct->children.end();
|
i != acct->children.end();
|
||||||
i++)
|
i++)
|
||||||
display_total(out, balance, (*i).second, ! displayed, headlines);
|
display_total(out, balance, (*i).second, level, headlines);
|
||||||
}
|
}
|
||||||
|
|
||||||
void report_balances(std::ostream& out, regexps_map& regexps)
|
void report_balances(std::ostream& out, regexps_list& regexps)
|
||||||
{
|
{
|
||||||
// Walk through all of the ledger entries, computing the account
|
// Walk through all of the ledger entries, computing the account
|
||||||
// totals
|
// totals
|
||||||
|
|
@ -105,7 +230,8 @@ void report_balances(std::ostream& out, regexps_map& regexps)
|
||||||
for (entries_list_iterator i = main_ledger->entries.begin();
|
for (entries_list_iterator i = main_ledger->entries.begin();
|
||||||
i != main_ledger->entries.end();
|
i != main_ledger->entries.end();
|
||||||
i++) {
|
i++) {
|
||||||
if ((cleared_only && ! (*i)->cleared) || ! matches_date_range(*i))
|
if ((cleared_only && ! (*i)->cleared) ||
|
||||||
|
(uncleared_only && (*i)->cleared) || ! matches_date_range(*i))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
for (std::list<transaction *>::iterator x = (*i)->xacts.begin();
|
for (std::list<transaction *>::iterator x = (*i)->xacts.begin();
|
||||||
|
|
@ -117,16 +243,17 @@ void report_balances(std::ostream& out, regexps_map& regexps)
|
||||||
for (account * acct = (*x)->acct;
|
for (account * acct = (*x)->acct;
|
||||||
acct;
|
acct;
|
||||||
acct = show_subtotals ? acct->parent : NULL) {
|
acct = show_subtotals ? acct->parent : NULL) {
|
||||||
|
bool by_exclusion = false;
|
||||||
|
bool match = false;
|
||||||
|
|
||||||
if (acct->checked == 0) {
|
if (acct->checked == 0) {
|
||||||
if (regexps.empty()) {
|
if (regexps.empty()) {
|
||||||
if (! (show_children || ! acct->parent))
|
if (! (show_children || ! acct->parent))
|
||||||
acct->checked = 2;
|
acct->checked = 2;
|
||||||
else
|
else
|
||||||
acct->checked = 1;
|
acct->checked = 1;
|
||||||
}
|
} else {
|
||||||
else {
|
match = matches(regexps, acct->as_str(), &by_exclusion);
|
||||||
bool by_exclusion = false;
|
|
||||||
bool match = matches(regexps, acct->as_str(), &by_exclusion);
|
|
||||||
if (! match) {
|
if (! match) {
|
||||||
acct->checked = 2;
|
acct->checked = 2;
|
||||||
}
|
}
|
||||||
|
|
@ -144,9 +271,26 @@ void report_balances(std::ostream& out, regexps_map& regexps)
|
||||||
|
|
||||||
if (acct->checked == 1) {
|
if (acct->checked == 1) {
|
||||||
amount * street = (*x)->cost->street(get_quotes);
|
amount * street = (*x)->cost->street(get_quotes);
|
||||||
|
if (cost_basis &&
|
||||||
|
street->commdty() == (*x)->cost->commdty() &&
|
||||||
|
(*x)->cost->has_price()) {
|
||||||
|
street = (*x)->cost->value();
|
||||||
|
}
|
||||||
acct->balance.credit(street);
|
acct->balance.credit(street);
|
||||||
delete street;
|
delete street;
|
||||||
}
|
}
|
||||||
|
else if (show_subtotals) {
|
||||||
|
if (! regexps.empty() && ! match) {
|
||||||
|
for (account * a = acct->parent; a; a = a->parent) {
|
||||||
|
if (matches(regexps, a->as_str(), &by_exclusion) &&
|
||||||
|
! by_exclusion) {
|
||||||
|
match = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (! match) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -159,12 +303,14 @@ void report_balances(std::ostream& out, regexps_map& regexps)
|
||||||
|
|
||||||
for (accounts_map_iterator i = main_ledger->accounts.begin();
|
for (accounts_map_iterator i = main_ledger->accounts.begin();
|
||||||
i != main_ledger->accounts.end();
|
i != main_ledger->accounts.end();
|
||||||
i++)
|
i++) {
|
||||||
display_total(out, balance, (*i).second, true, &headlines);
|
adjust_total((*i).second);
|
||||||
|
display_total(out, balance, (*i).second, 0, &headlines);
|
||||||
|
}
|
||||||
|
|
||||||
// Print the total of all the balances shown
|
// Print the total of all the balances shown
|
||||||
|
|
||||||
if (show_subtotals && headlines > 1) {
|
if (show_subtotals && headlines > 1 && ! balance.is_zero()) {
|
||||||
out << "--------------------" << std::endl;
|
out << "--------------------" << std::endl;
|
||||||
balance.print(out, 20);
|
balance.print(out, 20);
|
||||||
out << std::endl;
|
out << std::endl;
|
||||||
|
|
@ -188,98 +334,183 @@ static std::string truncated(const std::string& str, int width)
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
void print_register(const std::string& acct_name, std::ostream& out,
|
enum periodicity_t {
|
||||||
regexps_map& regexps)
|
PERIOD_NONE,
|
||||||
|
PERIOD_MONTHLY,
|
||||||
|
PERIOD_WEEKLY_SUN,
|
||||||
|
PERIOD_WEEKLY_MON
|
||||||
|
};
|
||||||
|
|
||||||
|
void print_register_transaction(std::ostream& out, entry *ent,
|
||||||
|
transaction *xact, totals& balance)
|
||||||
|
{
|
||||||
|
char buf[32];
|
||||||
|
std::strftime(buf, 31, "%m/%d ", std::localtime(&ent->date));
|
||||||
|
out << buf;
|
||||||
|
|
||||||
|
out.width(25);
|
||||||
|
if (ent->desc.empty())
|
||||||
|
out << " ";
|
||||||
|
else
|
||||||
|
out << std::left << truncated(ent->desc, 25);
|
||||||
|
out << " ";
|
||||||
|
|
||||||
|
// Always display the street value, if prices have been
|
||||||
|
// specified
|
||||||
|
|
||||||
|
amount * street = xact->cost->street(get_quotes);
|
||||||
|
balance.credit(street);
|
||||||
|
|
||||||
|
// If there are two transactions, use the one which does not
|
||||||
|
// refer to this account. If there are more than two, print
|
||||||
|
// "<Splits...>", unless the -s option is being used (show
|
||||||
|
// children), in which case print all of the splits, like
|
||||||
|
// gnucash does.
|
||||||
|
|
||||||
|
transaction * xp;
|
||||||
|
if (ent->xacts.size() == 2) {
|
||||||
|
if (xact == ent->xacts.front())
|
||||||
|
xp = ent->xacts.back();
|
||||||
|
else
|
||||||
|
xp = ent->xacts.front();
|
||||||
|
} else {
|
||||||
|
xp = xact;
|
||||||
|
}
|
||||||
|
std::string xact_str = xp->acct_as_str();
|
||||||
|
|
||||||
|
if (xp == xact && ! show_subtotals)
|
||||||
|
xact_str = "<Splits...>";
|
||||||
|
|
||||||
|
out.width(22);
|
||||||
|
out << std::left << truncated(xact_str, 22) << " ";
|
||||||
|
|
||||||
|
out.width(12);
|
||||||
|
out << std::right << street->as_str(true);
|
||||||
|
delete street;
|
||||||
|
|
||||||
|
balance.print(out, 12);
|
||||||
|
|
||||||
|
out << std::endl;
|
||||||
|
|
||||||
|
if (! show_children || xp != xact)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (std::list<transaction *>::iterator y = ent->xacts.begin();
|
||||||
|
y != ent->xacts.end();
|
||||||
|
y++) {
|
||||||
|
if (xact == *y)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
out << " ";
|
||||||
|
|
||||||
|
out.width(22);
|
||||||
|
out << std::left << truncated((*y)->acct_as_str(), 22) << " ";
|
||||||
|
|
||||||
|
out.width(12);
|
||||||
|
street = (*y)->cost->street(get_quotes);
|
||||||
|
out << std::right << street->as_str(true) << std::endl;
|
||||||
|
delete street;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_register_period(std::ostream& out, std::time_t date,
|
||||||
|
account *acct, amount& sum, totals& balance)
|
||||||
|
{
|
||||||
|
char buf[32];
|
||||||
|
std::strftime(buf, 31, "%Y/%m/%d ", std::localtime(&date));
|
||||||
|
out << buf;
|
||||||
|
|
||||||
|
if (! gnuplot_safe) {
|
||||||
|
out.width(20);
|
||||||
|
std::strftime(buf, 31, "%B", std::localtime(&date));
|
||||||
|
out << std::left << truncated(buf, 20);
|
||||||
|
out << " ";
|
||||||
|
|
||||||
|
out.width(22);
|
||||||
|
out << std::left << truncated(acct->as_str(), 22) << " ";
|
||||||
|
} else {
|
||||||
|
commodity * cmdty = sum.commdty();
|
||||||
|
cmdty->symbol = "";
|
||||||
|
cmdty->separate = false;
|
||||||
|
cmdty->thousands = false;
|
||||||
|
cmdty->european = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.width(12);
|
||||||
|
out << std::right << sum.as_str();
|
||||||
|
|
||||||
|
if (! gnuplot_safe)
|
||||||
|
balance.print(out, 12);
|
||||||
|
|
||||||
|
out << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_register(std::ostream& out, const std::string& acct_name,
|
||||||
|
regexps_list& regexps, periodicity_t period = PERIOD_NONE)
|
||||||
{
|
{
|
||||||
mask acct_regex(acct_name);
|
mask acct_regex(acct_name);
|
||||||
|
|
||||||
// Walk through all of the ledger entries, printing their register
|
// Walk through all of the ledger entries, printing their register
|
||||||
// formatted equivalent
|
// formatted equivalent
|
||||||
|
|
||||||
totals balance;
|
totals balance;
|
||||||
|
amount * period_sum = NULL; // jww (2004-04-27): should be 'totals' type
|
||||||
|
std::time_t last_date;
|
||||||
|
account * last_acct;
|
||||||
|
int last_mon = -1;
|
||||||
|
|
||||||
for (entries_list_iterator i = main_ledger->entries.begin();
|
for (entries_list_iterator i = main_ledger->entries.begin();
|
||||||
i != main_ledger->entries.end();
|
i != main_ledger->entries.end();
|
||||||
i++) {
|
i++) {
|
||||||
if ((cleared_only && ! (*i)->cleared) ||
|
if ((cleared_only && ! (*i)->cleared) ||
|
||||||
|
(uncleared_only && (*i)->cleared) ||
|
||||||
! matches_date_range(*i) || ! (*i)->matches(regexps))
|
! matches_date_range(*i) || ! (*i)->matches(regexps))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
int entry_mon = std::localtime(&(*i)->date)->tm_mon;
|
||||||
|
|
||||||
|
if (period_sum && period == PERIOD_MONTHLY &&
|
||||||
|
last_mon != -1 && entry_mon != last_mon) {
|
||||||
|
assert(last_acct);
|
||||||
|
print_register_period(out, last_date, last_acct,
|
||||||
|
*period_sum, balance);
|
||||||
|
delete period_sum;
|
||||||
|
period_sum = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
for (std::list<transaction *>::iterator x = (*i)->xacts.begin();
|
for (std::list<transaction *>::iterator x = (*i)->xacts.begin();
|
||||||
x != (*i)->xacts.end();
|
x != (*i)->xacts.end();
|
||||||
x++) {
|
x++) {
|
||||||
if (! acct_regex.match((*x)->acct->as_str()))
|
if (! acct_regex.match((*x)->acct->as_str()) ||
|
||||||
|
(lower_limit && ! satisfies_limit((*x)->cost)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
char buf[32];
|
if (period == PERIOD_NONE) {
|
||||||
std::strftime(buf, 31, "%m/%d ", std::localtime(&(*i)->date));
|
print_register_transaction(out, *i, *x, balance);
|
||||||
out << buf;
|
|
||||||
|
|
||||||
out.width(25);
|
|
||||||
if ((*i)->desc.empty())
|
|
||||||
out << " ";
|
|
||||||
else
|
|
||||||
out << std::left << truncated((*i)->desc, 25);
|
|
||||||
out << " ";
|
|
||||||
|
|
||||||
// Always display the street value, if prices have been
|
|
||||||
// specified
|
|
||||||
|
|
||||||
amount * street = (*x)->cost->street(get_quotes);
|
|
||||||
balance.credit(street);
|
|
||||||
|
|
||||||
// If there are two transactions, use the one which does not
|
|
||||||
// refer to this account. If there are more than two, print
|
|
||||||
// "<Splits...>", unless the -s option is being used (show
|
|
||||||
// children), in which case print all of the splits, like
|
|
||||||
// gnucash does.
|
|
||||||
|
|
||||||
transaction * xact;
|
|
||||||
if (! full_names && (*i)->xacts.size() == 2) {
|
|
||||||
if (*x == (*i)->xacts.front())
|
|
||||||
xact = (*i)->xacts.back();
|
|
||||||
else
|
|
||||||
xact = (*i)->xacts.front();
|
|
||||||
} else {
|
} else {
|
||||||
xact = *x;
|
amount * street = (*x)->cost->street(get_quotes);
|
||||||
}
|
balance.credit(street);
|
||||||
std::string xact_str = xact->acct_as_str();
|
|
||||||
|
|
||||||
if (xact == *x && ! show_subtotals)
|
if (period_sum) {
|
||||||
xact_str = "<Splits...>";
|
period_sum->credit(street);
|
||||||
|
delete street;
|
||||||
|
} else {
|
||||||
|
period_sum = street;
|
||||||
|
}
|
||||||
|
|
||||||
out.width(22);
|
last_acct = (*x)->acct;
|
||||||
out << std::left << truncated(xact_str, 22) << " ";
|
last_date = (*i)->date;
|
||||||
|
last_mon = entry_mon;
|
||||||
out.width(12);
|
|
||||||
out << std::right << street->as_str(true);
|
|
||||||
delete street;
|
|
||||||
|
|
||||||
balance.print(out, 12);
|
|
||||||
|
|
||||||
out << std::endl;
|
|
||||||
|
|
||||||
if (! show_children || xact != *x)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
for (std::list<transaction *>::iterator y = (*i)->xacts.begin();
|
|
||||||
y != (*i)->xacts.end();
|
|
||||||
y++) {
|
|
||||||
if (*x == *y)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
out << " ";
|
|
||||||
|
|
||||||
out.width(22);
|
|
||||||
out << std::left << truncated((*y)->acct_as_str(), 22) << " ";
|
|
||||||
|
|
||||||
out.width(12);
|
|
||||||
street = (*y)->cost->street(get_quotes);
|
|
||||||
out << std::right << street->as_str(true) << std::endl;
|
|
||||||
delete street;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (period_sum) {
|
||||||
|
if (last_acct)
|
||||||
|
print_register_period(out, last_date, last_acct,
|
||||||
|
*period_sum, balance);
|
||||||
|
delete period_sum;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
@ -289,14 +520,14 @@ void print_register(const std::string& acct_name, std::ostream& out,
|
||||||
// balances.
|
// balances.
|
||||||
//
|
//
|
||||||
|
|
||||||
static void equity_entry(account * acct, regexps_map& regexps,
|
static void equity_entry(account * acct, regexps_list& regexps,
|
||||||
std::ostream& out)
|
std::ostream& out)
|
||||||
{
|
{
|
||||||
if (! acct->balance.is_zero() &&
|
if (! acct->balance.is_zero() &&
|
||||||
(regexps.empty() || matches(regexps, acct->as_str()))) {
|
(regexps.empty() || matches(regexps, acct->as_str()))) {
|
||||||
entry opening(main_ledger);
|
entry opening(main_ledger);
|
||||||
|
|
||||||
opening.date = std::time(NULL);
|
opening.date = have_ending ? end_date : std::time(NULL);
|
||||||
opening.cleared = true;
|
opening.cleared = true;
|
||||||
opening.desc = "Opening Balance";
|
opening.desc = "Opening Balance";
|
||||||
|
|
||||||
|
|
@ -330,7 +561,7 @@ static void equity_entry(account * acct, regexps_map& regexps,
|
||||||
equity_entry((*i).second, regexps, out);
|
equity_entry((*i).second, regexps, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
void equity_ledger(std::ostream& out, regexps_map& regexps)
|
void equity_ledger(std::ostream& out, regexps_list& regexps)
|
||||||
{
|
{
|
||||||
// The account have their current totals already generated as a
|
// The account have their current totals already generated as a
|
||||||
// result of parsing. We just have to output those values.
|
// result of parsing. We just have to output those values.
|
||||||
|
|
@ -347,9 +578,9 @@ void equity_ledger(std::ostream& out, regexps_map& regexps)
|
||||||
|
|
||||||
void add_new_entry(int index, int argc, char **argv)
|
void add_new_entry(int index, int argc, char **argv)
|
||||||
{
|
{
|
||||||
regexps_map regexps;
|
regexps_list regexps;
|
||||||
entry added(main_ledger);
|
entry added(main_ledger);
|
||||||
entry * matching = NULL;
|
entry * matching = NULL;
|
||||||
|
|
||||||
assert(index < argc);
|
assert(index < argc);
|
||||||
|
|
||||||
|
|
@ -423,14 +654,17 @@ void add_new_entry(int index, int argc, char **argv)
|
||||||
|
|
||||||
account * acct = NULL;
|
account * acct = NULL;
|
||||||
commodity * cmdty = NULL;
|
commodity * cmdty = NULL;
|
||||||
for (std::list<transaction *>::iterator x = matching->xacts.begin();
|
|
||||||
x != matching->xacts.end();
|
if (matching) {
|
||||||
x++) {
|
for (std::list<transaction *>::iterator x = matching->xacts.begin();
|
||||||
if (acct_regex.match((*x)->acct->as_str())) {
|
x != matching->xacts.end();
|
||||||
acct = (*x)->acct;
|
x++) {
|
||||||
cmdty = (*x)->cost->commdty();
|
if (acct_regex.match((*x)->acct->as_str())) {
|
||||||
break;
|
acct = (*x)->acct;
|
||||||
}
|
cmdty = (*x)->cost->commdty();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (acct)
|
if (acct)
|
||||||
|
|
@ -466,7 +700,12 @@ void add_new_entry(int index, int argc, char **argv)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
transaction * xact = new transaction();
|
transaction * xact = new transaction();
|
||||||
xact->acct = matching->xacts.back()->acct;
|
if (! matching) {
|
||||||
|
std::cerr << "Error: Could not figure out the account to draw from."
|
||||||
|
<< std::endl;
|
||||||
|
std::exit(1);
|
||||||
|
}
|
||||||
|
xact->acct = matching->xacts.back()->acct;
|
||||||
xact->cost = NULL;
|
xact->cost = NULL;
|
||||||
added.xacts.push_back(xact);
|
added.xacts.push_back(xact);
|
||||||
}
|
}
|
||||||
|
|
@ -480,8 +719,8 @@ void add_new_entry(int index, int argc, char **argv)
|
||||||
// "wash" ugly ledger files. It's written here, instead of ledger.cc,
|
// "wash" ugly ledger files. It's written here, instead of ledger.cc,
|
||||||
// in order to access the static globals above.
|
// in order to access the static globals above.
|
||||||
|
|
||||||
void book::print(std::ostream& out, regexps_map& regexps,
|
void book::print(std::ostream& out, regexps_list& regexps,
|
||||||
bool shortcut) const
|
bool shortcut) const
|
||||||
{
|
{
|
||||||
for (entries_list_const_iterator i = entries.begin();
|
for (entries_list_const_iterator i = entries.begin();
|
||||||
i != entries.end();
|
i != entries.end();
|
||||||
|
|
@ -506,25 +745,33 @@ static void show_help(std::ostream& out)
|
||||||
<< " -b DATE specify a beginning date" << std::endl
|
<< " -b DATE specify a beginning date" << std::endl
|
||||||
<< " -e DATE specify an ending date" << std::endl
|
<< " -e DATE specify an ending date" << std::endl
|
||||||
<< " -c do not show future entries (same as -e TODAY)" << std::endl
|
<< " -c do not show future entries (same as -e TODAY)" << std::endl
|
||||||
<< " -C also show cleared transactions" << std::endl
|
<< " -C show only cleared transactions and balances" << std::endl
|
||||||
<< " -d DATE specify a date mask ('-d mon', for all mondays)" << std::endl
|
<< " -d DATE specify a date mask ('-d mon', for all mondays)" << std::endl
|
||||||
|
<< " -E also show accounts with zero totals" << std::endl
|
||||||
<< " -f FILE specify pathname of ledger data file" << std::endl
|
<< " -f FILE specify pathname of ledger data file" << std::endl
|
||||||
<< " -F print each account's full name" << std::endl
|
<< " -F print each account's full name" << std::endl
|
||||||
<< " -h display this help text" << std::endl
|
<< " -h display this help text" << std::endl
|
||||||
<< " -i FILE read the list of inclusion regexps from FILE" << std::endl
|
<< " -i FILE read the list of inclusion regexps from FILE" << std::endl
|
||||||
<< " -n do not generate totals for parent accounts" << std::endl
|
<< " -l AMT don't print balance totals whose abs value is <AMT" << std::endl
|
||||||
|
<< " -M print register using monthly sub-totals" << std::endl
|
||||||
|
<< " -G use with -M to produce gnuplot-friendly output" << std::endl
|
||||||
|
<< " -n do not calculate parent account totals" << std::endl
|
||||||
|
<< " -N REGEX accounts matching REGEXP only display if negative" << std::endl
|
||||||
<< " -p ARG set a price, or read prices from a file" << std::endl
|
<< " -p ARG set a price, or read prices from a file" << std::endl
|
||||||
<< " -P download price quotes from the Internet" << std::endl
|
<< " -P download price quotes from the Internet" << std::endl
|
||||||
<< " (works by running the command \"getquote SYMBOL\")" << std::endl
|
<< " (works by running the command \"getquote SYMBOL\")" << std::endl
|
||||||
<< " -R do not factor in virtual transactions" << std::endl
|
<< " -R do not factor in virtual transactions" << std::endl
|
||||||
<< " -s show sub-accounts in balance totals" << std::endl
|
<< " -s show sub-accounts in balance totals" << std::endl
|
||||||
<< " -S show empty accounts in balance totals" << std::endl
|
<< " -S sort the output of \"print\" by date" << std::endl
|
||||||
|
<< " -U show only uncleared transactions and balances" << std::endl
|
||||||
<< " -v display version information" << std::endl << std::endl
|
<< " -v display version information" << std::endl << std::endl
|
||||||
<< "commands:" << std::endl
|
<< "commands:" << std::endl
|
||||||
<< " balance show balance totals" << std::endl
|
<< " balance show balance totals" << std::endl
|
||||||
<< " register display a register for ACCOUNT" << std::endl
|
<< " register display a register for ACCOUNT" << std::endl
|
||||||
<< " print print all ledger entries" << std::endl
|
<< " print print all ledger entries" << std::endl
|
||||||
<< " equity generate equity ledger for all entries" << std::endl;
|
<< " equity generate equity ledger for all entries" << std::endl
|
||||||
|
<< " entry output a newly formed entry, based on arguments"
|
||||||
|
<< std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
@ -534,15 +781,18 @@ static void show_help(std::ostream& out)
|
||||||
|
|
||||||
int main(int argc, char * argv[])
|
int main(int argc, char * argv[])
|
||||||
{
|
{
|
||||||
std::istream * file = NULL;
|
int index;
|
||||||
std::string prices;
|
std::string prices;
|
||||||
regexps_map regexps;
|
std::string limit;
|
||||||
int index;
|
regexps_list regexps;
|
||||||
|
|
||||||
|
std::vector<std::string> files;
|
||||||
|
|
||||||
// Parse the command-line options
|
// Parse the command-line options
|
||||||
|
|
||||||
int c;
|
int c;
|
||||||
while (-1 != (c = getopt(argc, argv, "+b:e:d:cChRV:f:i:p:PvsSEnF"))) {
|
while (-1 != (c = getopt(argc, argv,
|
||||||
|
"+b:e:d:cCUhBRV:f:i:p:PvsSEnFMGl:N:"))) {
|
||||||
switch (char(c)) {
|
switch (char(c)) {
|
||||||
case 'b':
|
case 'b':
|
||||||
have_beginning = true;
|
have_beginning = true;
|
||||||
|
|
@ -574,19 +824,27 @@ int main(int argc, char * argv[])
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'h': show_help(std::cout); break;
|
case 'h': show_help(std::cout); break;
|
||||||
case 'f': file = new std::ifstream(optarg); break;
|
case 'f': files.push_back(optarg); break;
|
||||||
|
|
||||||
case 'C': cleared_only = true; break;
|
case 'C': cleared_only = true; break;
|
||||||
|
case 'U': uncleared_only = true; break;
|
||||||
|
case 'B': cost_basis = true; break;
|
||||||
case 'R': show_virtual = false; break;
|
case 'R': show_virtual = false; break;
|
||||||
case 's': show_children = true; break;
|
case 's': show_children = true; break;
|
||||||
case 'S': show_sorted = true; break;
|
case 'S': show_sorted = true; break;
|
||||||
case 'E': show_empty = true; break;
|
case 'E': show_empty = true; break;
|
||||||
case 'n': show_subtotals = false; break;
|
case 'n': show_subtotals = false; break;
|
||||||
case 'F': full_names = true; break;
|
case 'F': full_names = true; break;
|
||||||
|
case 'M': print_monthly = true; break;
|
||||||
|
case 'G': gnuplot_safe = true; break;
|
||||||
|
|
||||||
|
case 'N':
|
||||||
|
negonly_regexp = new mask(optarg);
|
||||||
|
break;
|
||||||
|
|
||||||
// -i path-to-file-of-regexps
|
// -i path-to-file-of-regexps
|
||||||
case 'i':
|
case 'i':
|
||||||
read_regexps(optarg, regexps);
|
if (access(optarg, R_OK) != -1)
|
||||||
|
read_regexps(optarg, regexps);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// -p "COMMODITY=PRICE"
|
// -p "COMMODITY=PRICE"
|
||||||
|
|
@ -599,6 +857,10 @@ int main(int argc, char * argv[])
|
||||||
get_quotes = true;
|
get_quotes = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'l':
|
||||||
|
limit = optarg;
|
||||||
|
break;
|
||||||
|
|
||||||
case 'v':
|
case 'v':
|
||||||
std::cout
|
std::cout
|
||||||
<< "Ledger Accouting Tool " LEDGER_VERSION << std::endl
|
<< "Ledger Accouting Tool " LEDGER_VERSION << std::endl
|
||||||
|
|
@ -620,20 +882,6 @@ int main(int argc, char * argv[])
|
||||||
|
|
||||||
index = optind;
|
index = optind;
|
||||||
|
|
||||||
// A ledger data file must be specified
|
|
||||||
|
|
||||||
if (! file) {
|
|
||||||
const char * p = std::getenv("LEDGER");
|
|
||||||
if (p)
|
|
||||||
file = new std::ifstream(p);
|
|
||||||
|
|
||||||
if (! file || ! *file) {
|
|
||||||
std::cerr << ("Please specify ledger file using -f option "
|
|
||||||
"or LEDGER environment variable.") << std::endl;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the command word
|
// Read the command word
|
||||||
|
|
||||||
const std::string command = argv[index++];
|
const std::string command = argv[index++];
|
||||||
|
|
@ -655,49 +903,64 @@ int main(int argc, char * argv[])
|
||||||
for (; index < argc; index++)
|
for (; index < argc; index++)
|
||||||
regexps.push_back(mask(argv[index]));
|
regexps.push_back(mask(argv[index]));
|
||||||
|
|
||||||
// Parse the ledger
|
// A ledger data file must be specified
|
||||||
|
|
||||||
#ifdef READ_GNUCASH
|
int entry_count = 0;
|
||||||
char buf[32];
|
|
||||||
file->get(buf, 31);
|
|
||||||
file->seekg(0);
|
|
||||||
|
|
||||||
if (std::strncmp(buf, "<?xml version=\"1.0\"?>", 21) == 0)
|
main_ledger = new book;
|
||||||
main_ledger = parse_gnucash(*file, command == "equity");
|
|
||||||
else
|
|
||||||
#endif
|
|
||||||
main_ledger = parse_ledger(*file, regexps, command == "equity");
|
|
||||||
|
|
||||||
delete file;
|
if (files.empty()) {
|
||||||
|
if (char * p = std::getenv("LEDGER")) {
|
||||||
|
for (p = std::strtok(p, ":"); p; p = std::strtok(NULL, ":")) {
|
||||||
|
char * sep = std::strrchr(p, '=');
|
||||||
|
if (sep) *sep++ = '\0';
|
||||||
|
entry_count += parse_ledger_file(main_ledger, std::string(p),
|
||||||
|
regexps, command == "equity", sep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (std::vector<std::string>::iterator i = files.begin();
|
||||||
|
i != files.end(); i++) {
|
||||||
|
char buf[4096];
|
||||||
|
char * p = buf;
|
||||||
|
std::strcpy(p, (*i).c_str());
|
||||||
|
char * sep = std::strrchr(p, '=');
|
||||||
|
if (sep) *sep++ = '\0';
|
||||||
|
entry_count += parse_ledger_file(main_ledger, std::string(p),
|
||||||
|
regexps, command == "equity", sep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (! main_ledger)
|
if (entry_count == 0) {
|
||||||
|
std::cerr << ("Please specify ledger file(s) using -f option "
|
||||||
|
"or LEDGER environment variable.") << std::endl;
|
||||||
return 1;
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Record any prices specified by the user
|
// Record any prices specified by the user
|
||||||
|
|
||||||
if (! prices.empty()) {
|
if (! prices.empty()) {
|
||||||
if (access(prices.c_str(), R_OK) != -1) {
|
if (access(prices.c_str(), R_OK) != -1)
|
||||||
std::ifstream pricedb(prices.c_str());
|
read_prices(prices);
|
||||||
while (! pricedb.eof()) {
|
else
|
||||||
char buf[80];
|
|
||||||
pricedb.getline(buf, 79);
|
|
||||||
if (*buf && ! std::isspace(*buf))
|
|
||||||
parse_price_setting(buf);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
parse_price_setting(prices);
|
parse_price_setting(prices);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse the lower limit, if specified
|
||||||
|
|
||||||
|
if (! limit.empty())
|
||||||
|
lower_limit = create_amount(limit);
|
||||||
|
|
||||||
// Process the command
|
// Process the command
|
||||||
|
|
||||||
if (command == "balance" || command == "bal") {
|
if (command == "balance" || command == "bal") {
|
||||||
report_balances(std::cout, regexps);
|
report_balances(std::cout, regexps);
|
||||||
}
|
}
|
||||||
else if (command == "register" || command == "reg") {
|
else if (command == "register" || command == "reg") {
|
||||||
if (show_sorted)
|
if (show_sorted || print_monthly)
|
||||||
main_ledger->sort(cmp_entry_date());
|
main_ledger->sort(cmp_entry_date());
|
||||||
print_register(argv[name_index], std::cout, regexps);
|
print_register(std::cout, argv[name_index], regexps,
|
||||||
|
print_monthly ? PERIOD_MONTHLY : PERIOD_NONE);
|
||||||
}
|
}
|
||||||
else if (command == "print") {
|
else if (command == "print") {
|
||||||
if (show_sorted)
|
if (show_sorted)
|
||||||
|
|
@ -721,6 +984,12 @@ int main(int argc, char * argv[])
|
||||||
// process is about to give back its heap to the OS.
|
// process is about to give back its heap to the OS.
|
||||||
|
|
||||||
delete main_ledger;
|
delete main_ledger;
|
||||||
|
|
||||||
|
if (lower_limit)
|
||||||
|
delete lower_limit;
|
||||||
|
|
||||||
|
if (negonly_regexp)
|
||||||
|
delete negonly_regexp;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
5
scripts/README
Normal file
5
scripts/README
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
This scripts are provided just in the way of giving ideas. They
|
||||||
|
probably all need to be modified to suit your particular environment.
|
||||||
|
Beware!
|
||||||
|
|
||||||
|
John
|
||||||
27
scripts/bal
Executable file
27
scripts/bal
Executable file
|
|
@ -0,0 +1,27 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
switch=""
|
||||||
|
current="-c"
|
||||||
|
limit="-l \$50"
|
||||||
|
negonly="-N ^Liabilities"
|
||||||
|
|
||||||
|
if [ "$1" = "-C" -o "$1" = "-U" -o "$1" = "-P" ]; then
|
||||||
|
switch="$1"
|
||||||
|
shift
|
||||||
|
elif [ "$1" = "-b" -o "$1" = "-e" ]; then
|
||||||
|
current="$1 $2"
|
||||||
|
shift 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
accts="$@"
|
||||||
|
if [ -z "$accts" ]; then
|
||||||
|
accts="-Equity -Income -Expenses"
|
||||||
|
if [ ! "$switch" = "-P" ]; then
|
||||||
|
accts="$accts -Savings -Retirement"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
limit=""
|
||||||
|
negonly=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
ledger $current $limit $negonly -s $switch balance $accts
|
||||||
16
scripts/getquote
Executable file
16
scripts/getquote
Executable file
|
|
@ -0,0 +1,16 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
exit 0 if $ARGV[0] eq "\$";
|
||||||
|
|
||||||
|
use Finance::Quote;
|
||||||
|
|
||||||
|
$q = Finance::Quote->new;
|
||||||
|
|
||||||
|
$q->timeout(60);
|
||||||
|
$q->require_labels(qw/price/);
|
||||||
|
|
||||||
|
%quotes = $q->fetch("nasdaq", $ARGV[0]);
|
||||||
|
|
||||||
|
if ($quotes{$ARGV[0], "price"}) {
|
||||||
|
print "\$", $quotes{$ARGV[0], "price"}, "\n";
|
||||||
|
}
|
||||||
27
scripts/mean
Executable file
27
scripts/mean
Executable file
|
|
@ -0,0 +1,27 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
$last = $ARGV[-1];
|
||||||
|
splice(@ARGV, -1);
|
||||||
|
|
||||||
|
open(PIPE, "ledger -MG @ARGV register $last |") || die;
|
||||||
|
@values = ();
|
||||||
|
while (<PIPE>) {
|
||||||
|
($date, $value) = split;
|
||||||
|
push @values, $value;
|
||||||
|
}
|
||||||
|
close(PIPE);
|
||||||
|
|
||||||
|
@values = sort @values;
|
||||||
|
splice(@values, 0, 1);
|
||||||
|
splice(@values, -1);
|
||||||
|
|
||||||
|
$value = 0.0;
|
||||||
|
for $item (@values) {
|
||||||
|
$value += $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@values) {
|
||||||
|
printf("%.2f\n", $value / @values);
|
||||||
|
} else {
|
||||||
|
die "There are no values to average!\n";
|
||||||
|
}
|
||||||
2
scripts/profit
Executable file
2
scripts/profit
Executable file
|
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/sh
|
||||||
|
ledger "$@" balance ^Income$ ^Expenses$
|
||||||
14
scripts/reg
Executable file
14
scripts/reg
Executable file
|
|
@ -0,0 +1,14 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
switch="-U"
|
||||||
|
current="-c"
|
||||||
|
|
||||||
|
if [ "$1" = "-C" -o "$1" = "-U" -o "$1" = "-P" -o "$1" = "-M" ]; then
|
||||||
|
switch="$1"
|
||||||
|
shift
|
||||||
|
elif [ "$1" = "-b" -o "$1" = "-e" ]; then
|
||||||
|
current="$1 $2"
|
||||||
|
shift 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
ledger $current -s $switch register "$@"
|
||||||
17
scripts/report
Executable file
17
scripts/report
Executable file
|
|
@ -0,0 +1,17 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
dir=$HOME/doc/finance
|
||||||
|
|
||||||
|
cd /tmp
|
||||||
|
|
||||||
|
ledger -M -G register "$@" > $1
|
||||||
|
|
||||||
|
gnuplot <<EOF
|
||||||
|
set terminal png
|
||||||
|
set output "report.png"
|
||||||
|
set xdata time
|
||||||
|
set timefmt "%Y/%m/%d"
|
||||||
|
plot "$1" using 1:2 with linespoints
|
||||||
|
EOF
|
||||||
|
|
||||||
|
open report.png
|
||||||
8
scripts/spending
Executable file
8
scripts/spending
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/bin/sh
|
||||||
|
ledger "$@" balance \
|
||||||
|
Expenses:Food \
|
||||||
|
Expenses:Movies \
|
||||||
|
Expenses:Auto:Gas \
|
||||||
|
Expenses:Tips \
|
||||||
|
Expenses:Health \
|
||||||
|
Expenses:Supplies
|
||||||
2
scripts/worth
Executable file
2
scripts/worth
Executable file
|
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/sh
|
||||||
|
ledger "$@" balance ^Assets$ ^Liabilities$
|
||||||
Loading…
Add table
Reference in a new issue