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
|
||||
OBJS = $(patsubst %.cc,%.o,$(CODE))
|
||||
CXX = cc
|
||||
#CXX = g++
|
||||
CFLAGS = #-Wall -ansi -pedantic
|
||||
DFLAGS = -O3 -fomit-frame-pointer
|
||||
#DFLAGS = -g -DDEBUG=1
|
||||
INCS = -I/usr/local/include
|
||||
LIBS = -L/usr/local/lib -lgmpxx -lgmp -lpcre
|
||||
#DFLAGS = -O3 -fomit-frame-pointer
|
||||
DFLAGS = -g -DDEBUG=1
|
||||
INCS = -I/sw/include -I/usr/include/gcc/darwin/3.3/c++ -I/usr/include/gcc/darwin/3.3/c++/ppc-darwin
|
||||
LIBS = -L/sw/lib -lgmpxx -lgmp -lpcre
|
||||
|
||||
ifdef GNUCASH
|
||||
CODE := $(CODE) gnucash.cc
|
||||
|
|
@ -15,17 +17,24 @@ endif
|
|||
|
||||
all: make.deps ledger ledger.info
|
||||
|
||||
install: all
|
||||
strip ledger
|
||||
cp ledger $(HOME)/bin
|
||||
|
||||
ledger: $(OBJS)
|
||||
g++ $(CFLAGS) $(INCS) $(DFLAGS) -o $@ $(OBJS) $(LIBS)
|
||||
$(CXX) $(CFLAGS) $(INCS) $(DFLAGS) -o $@ $(OBJS) $(LIBS)
|
||||
|
||||
ledger.info: ledger.texi
|
||||
makeinfo $<
|
||||
|
||||
%.o: %.cc
|
||||
g++ $(CFLAGS) $(INCS) $(DFLAGS) -c -o $@ $<
|
||||
$(CXX) $(CFLAGS) $(INCS) $(DFLAGS) -c -o $@ $<
|
||||
|
||||
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
|
||||
|
||||
|
|
|
|||
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
|
||||
|
||||
<contents>
|
||||
|
||||
* Building
|
||||
* Introduction
|
||||
|
||||
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 an accounting tool with the moxie to exist. It provides no
|
||||
bells or whistles, and returns the user to the days before user
|
||||
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
|
||||
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.
|
||||
depends only on the GNU multiprecision integer library (libgmp), and
|
||||
the Perl regular expression library (libpcre). It was developed using
|
||||
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:
|
||||
|
||||
<example>
|
||||
|
|
@ -21,110 +113,10 @@ make
|
|||
cp ledger /usr/local/bin
|
||||
</example>
|
||||
|
||||
* 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
|
||||
</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.
|
||||
Note that when building GNUmp, make sure to pass the =--enable-cxx=
|
||||
flag to configure, otherwise it will not build **libgmpxx.a**. And in
|
||||
case it is not already on your system, **xmlparse.h** is part of the
|
||||
libxmltok package, and not expat.
|
||||
|
||||
* Keeping a ledger
|
||||
|
||||
|
|
@ -302,7 +294,7 @@ FEQTX=$32
|
|||
Specify the prices file using the =-p= option:
|
||||
|
||||
<example>
|
||||
/home/johnw $ ledger -p prices.db balance brokerage
|
||||
ledger -p prices.db balance brokerage
|
||||
</example>
|
||||
|
||||
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:
|
||||
|
||||
<example>
|
||||
/home/johnw $ ledger -p "$=0.00280112 AU" balance checking
|
||||
ledger -p "$=0.00280112 AU" balance checking
|
||||
</example>
|
||||
|
||||
<example>
|
||||
|
|
@ -392,7 +384,7 @@ Where will money come from? The answer: your equity.
|
|||
<example>
|
||||
10/2 Opening Balance
|
||||
Assets:Checking $100.00
|
||||
Equity:Opening Balances $-100.00
|
||||
Equity:Opening Balances
|
||||
</example>
|
||||
|
||||
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
|
||||
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
|
||||
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.
|
||||
Something that stops many people from keeping a ledger at all is the
|
||||
insanity of tracking cash expenses. They rarely generate a receipt,
|
||||
and there are often a lot of small transactions, rather than a few
|
||||
large ones, as with checks.
|
||||
|
||||
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>
|
||||
/home/johnw $ export LEDGER=/home/johnw/doc/ledger.dat
|
||||
/home/johnw $ ledger balance
|
||||
2004/03/15 ATM
|
||||
Expenses:Cash $100.00
|
||||
Assets:Checking
|
||||
</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:
|
||||
If at some point you make a large cash expense that you want to track,
|
||||
just "move" the amount of the expense from "Expenses:Cash" into the
|
||||
target account:
|
||||
|
||||
<example>
|
||||
/home/johnw $ ledger balance expenses:food
|
||||
2004/03/20 Somebody
|
||||
Expenses:Food $65.00
|
||||
Expenses:Cash
|
||||
</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
|
||||
</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>
|
||||
This way, you can still track large cash expenses, while ignoring all
|
||||
of the smaller ones.
|
||||
|
||||
** 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
|
||||
"-R" flag, for "Reality".
|
||||
|
||||
*** Saving for a Special Occasion
|
||||
|
||||
*** Keeping a Budget
|
||||
|
||||
*** Tracking Allocated Funds
|
||||
Write about: Saving for a Special Occasion; Keeping a Budget; Tracking
|
||||
Allocated Funds.
|
||||
|
||||
** Automated transactions
|
||||
|
||||
*** 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.
|
||||
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
|
||||
interest, please consult 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:
|
||||
Ledger tool makes this extremely easy. Just set up the following
|
||||
automated transaction at the top of your ledger file:
|
||||
|
||||
<example>
|
||||
2002.12.31 Rent
|
||||
!Expenses:Rent $450.00
|
||||
Assets:Checking $-450.00
|
||||
; These entries will compute Huqúqu'lláh based on the
|
||||
; contents of the ledger.
|
||||
|
||||
= ^Income:
|
||||
= ^Expenses:Rent$
|
||||
= ^Expenses:Furnishings
|
||||
= ^Expenses:Business
|
||||
= ^Expenses:Taxes
|
||||
= ^Expenses:Insurance
|
||||
(Liabilities:Huqúqu'lláh) 0.19
|
||||
</example>
|
||||
|
||||
Even easier than that, simply put a list of regular expressions that
|
||||
match the categories you consider exempt in a file called =.huquq=,
|
||||
and the special marking will be done for you. Here is the file I use:
|
||||
This automated transaction works by looking at each transaction
|
||||
appearing afterward in the ledger file. If any match the account
|
||||
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>
|
||||
^Income:
|
||||
^Retirement:
|
||||
^Expenses:Rent
|
||||
^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
|
||||
2003/01/01 (101) Baha'i Huqúqu'lláh Trust
|
||||
Liabilities:Huqúqu'lláh $1,000.00
|
||||
Assets:Checking
|
||||
</example>
|
||||
|
||||
That's it. To see how much Huqúq is currently owed based on your
|
||||
ledger data, type:
|
||||
ledger entries, use:
|
||||
|
||||
<example>
|
||||
/home/johnw $ ledger balance ^huquq
|
||||
ledger balance Liabilities:Huqúq
|
||||
</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
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
The Ledger tool is fast and simple, but it offers no custom method for
|
||||
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
|
||||
|
|
@ -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
|
||||
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:
|
||||
[1] In some special cases, it will automatically balance the entry
|
||||
for you.
|
||||
|
|
|
|||
71
amount.cc
71
amount.cc
|
|
@ -55,7 +55,7 @@ class gmp_amount : public amount
|
|||
}
|
||||
|
||||
virtual amount * copy() const;
|
||||
virtual amount * value(amount *) const;
|
||||
virtual amount * value(const amount *) const;
|
||||
virtual amount * street(bool get_quotes) const;
|
||||
virtual bool has_price() const {
|
||||
return priced;
|
||||
|
|
@ -63,6 +63,8 @@ class gmp_amount : public amount
|
|||
virtual void set_value(const amount * val);
|
||||
|
||||
virtual bool is_zero() const;
|
||||
virtual bool is_negative() const;
|
||||
virtual int compare(const amount * other) const;
|
||||
|
||||
virtual void negate() {
|
||||
mpz_ui_sub(quantity, 0, quantity);
|
||||
|
|
@ -156,10 +158,10 @@ amount * gmp_amount::copy() const
|
|||
return new_amt;
|
||||
}
|
||||
|
||||
amount * gmp_amount::value(amount * pr) const
|
||||
amount * gmp_amount::value(const amount * pr) const
|
||||
{
|
||||
if (pr) {
|
||||
gmp_amount * p = dynamic_cast<gmp_amount *>(pr);
|
||||
const gmp_amount * p = dynamic_cast<const gmp_amount *>(pr);
|
||||
assert(p);
|
||||
|
||||
gmp_amount * new_amt = new gmp_amount();
|
||||
|
|
@ -298,6 +300,27 @@ bool gmp_amount::is_zero() const
|
|||
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,
|
||||
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(divisor);
|
||||
|
||||
if (comm == NULL)
|
||||
full_precision = true;
|
||||
|
||||
if (! full_precision && comm->precision < MAX_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;
|
||||
|
||||
if (comm->prefix) {
|
||||
if (comm && comm->prefix) {
|
||||
s << comm->symbol;
|
||||
if (comm->separate)
|
||||
s << " ";
|
||||
|
|
@ -348,7 +374,7 @@ static std::string amount_to_str(const commodity * comm, const mpz_t val,
|
|||
|
||||
if (mpz_sgn(quotient) == 0)
|
||||
s << '0';
|
||||
else if (! comm->thousands)
|
||||
else if (! comm || ! comm->thousands)
|
||||
s << quotient;
|
||||
else {
|
||||
bool printed = false;
|
||||
|
|
@ -371,22 +397,15 @@ static std::string amount_to_str(const commodity * comm, const mpz_t val,
|
|||
s << temp;
|
||||
|
||||
if (powers > 0) {
|
||||
if (comm->european)
|
||||
s << ".";
|
||||
else
|
||||
s << ",";
|
||||
|
||||
s << (comm && comm->european ? '.' : ',');
|
||||
printed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (comm->european)
|
||||
s << ',';
|
||||
else
|
||||
s << '.';
|
||||
s << (comm && comm->european ? ',' : '.');
|
||||
|
||||
if (! full_precision || mpz_sgn(rquotient) == 0) {
|
||||
if (comm && (! full_precision || mpz_sgn(rquotient) == 0)) {
|
||||
s.width(comm->precision);
|
||||
s.fill('0');
|
||||
s << rquotient;
|
||||
|
|
@ -399,17 +418,19 @@ static std::string amount_to_str(const commodity * comm, const mpz_t val,
|
|||
|
||||
width = MAX_PRECISION - width;
|
||||
|
||||
while (p >= buf && *p == '0' &&
|
||||
(p - buf) >= (comm->precision - width))
|
||||
p--;
|
||||
*(p + 1) = '\0';
|
||||
if (comm) {
|
||||
while (p >= buf && *p == '0' &&
|
||||
(p - buf) >= (comm->precision - width))
|
||||
p--;
|
||||
*(p + 1) = '\0';
|
||||
}
|
||||
|
||||
s.width(width + std::strlen(buf));
|
||||
s.fill('0');
|
||||
s << buf;
|
||||
}
|
||||
|
||||
if (! comm->prefix) {
|
||||
if (comm && ! comm->prefix) {
|
||||
if (comm->separate)
|
||||
s << " ";
|
||||
s << comm->symbol;
|
||||
|
|
@ -428,17 +449,11 @@ const std::string gmp_amount::as_str(bool full_prec) const
|
|||
{
|
||||
std::ostringstream s;
|
||||
|
||||
if (quantity_comm)
|
||||
s << amount_to_str(quantity_comm, quantity, full_prec);
|
||||
else
|
||||
s << quantity;
|
||||
s << amount_to_str(quantity_comm, quantity, full_prec);
|
||||
|
||||
if (priced) {
|
||||
s << " @ ";
|
||||
if (price_comm)
|
||||
s << amount_to_str(price_comm, price, full_prec);
|
||||
else
|
||||
s << price;
|
||||
s << amount_to_str(price_comm, price, full_prec);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
// If virtual accounts are being supported, walk through the
|
||||
// transactions and create new virtual transactions for all that
|
||||
// apply.
|
||||
// If automated transactions are being used, walk through the
|
||||
// current transaction lines and create new transactions for all
|
||||
// that match.
|
||||
|
||||
for (book::virtual_map_iterator m = ledger->virtual_mapping.begin();
|
||||
m != ledger->virtual_mapping.end();
|
||||
|
|
@ -278,7 +278,7 @@ bool entry::finalize(bool do_compute)
|
|||
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) ||
|
||||
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();
|
||||
x != xacts.end();
|
||||
x++) {
|
||||
if (ledger::matches(regexps, (*x)->acct->name) ||
|
||||
if (ledger::matches(regexps, (*x)->acct->as_str()) ||
|
||||
ledger::matches(regexps, (*x)->note)) {
|
||||
match = true;
|
||||
break;
|
||||
|
|
@ -331,6 +331,12 @@ void totals::credit(const totals& other)
|
|||
credit((*i).second);
|
||||
}
|
||||
|
||||
void totals::negate()
|
||||
{
|
||||
for (const_iterator i = amounts.begin(); i != amounts.end(); i++)
|
||||
(*i).second->negate();
|
||||
}
|
||||
|
||||
bool totals::is_zero() const
|
||||
{
|
||||
for (const_iterator i = amounts.begin(); i != amounts.end(); i++)
|
||||
|
|
@ -339,6 +345,19 @@ bool totals::is_zero() const
|
|||
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
|
||||
{
|
||||
bool first = true;
|
||||
|
|
@ -365,10 +384,12 @@ account::~account()
|
|||
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;
|
||||
else if (stop)
|
||||
return parent->as_str(stop) + ":" + name;
|
||||
else if (full_name.empty())
|
||||
full_name = parent->as_str() + ":" + name;
|
||||
|
||||
|
|
@ -409,20 +430,6 @@ mask::mask(const mask& m) : exclude(m.exclude), pattern(m.pattern)
|
|||
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
|
||||
{
|
||||
static int ovec[30];
|
||||
|
|
@ -431,67 +438,36 @@ bool mask::match(const std::string& str) const
|
|||
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)
|
||||
{
|
||||
assert(! regexps.empty());
|
||||
if (regexps.empty())
|
||||
return false;
|
||||
|
||||
bool match = false;
|
||||
bool definite = false;
|
||||
|
||||
// std::ofstream out("regex.out", std::ios_base::app);
|
||||
// out << "Matching against: " << str << std::endl;
|
||||
|
||||
for (regexps_map_const_iterator r = regexps.begin();
|
||||
for (regexps_list_const_iterator r = regexps.begin();
|
||||
r != regexps.end();
|
||||
r++) {
|
||||
// out << " Trying: " << (*r).pattern << std::endl;
|
||||
|
||||
static int ovec[30];
|
||||
int result = pcre_exec((*r).regexp, NULL, str.c_str(), str.length(),
|
||||
0, 0, ovec, 30);
|
||||
if (result >= 0) {
|
||||
// out << " Definite ";
|
||||
|
||||
match = ! (*r).exclude;
|
||||
// if (match)
|
||||
// out << "match";
|
||||
// else
|
||||
// out << "unmatch";
|
||||
|
||||
match = ! (*r).exclude;
|
||||
definite = true;
|
||||
}
|
||||
else if ((*r).exclude) {
|
||||
if (! match)
|
||||
match = ! definite;
|
||||
}
|
||||
else {
|
||||
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")
|
||||
// << std::endl;
|
||||
if (by_exclusion)
|
||||
*by_exclusion = match && ! definite && by_exclusion;
|
||||
|
||||
return match;
|
||||
}
|
||||
|
|
|
|||
143
ledger.el
143
ledger.el
|
|
@ -46,6 +46,13 @@
|
|||
:type 'file
|
||||
: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)
|
||||
(goto-char (point-min))
|
||||
(let* ((now (current-time))
|
||||
|
|
@ -86,12 +93,15 @@
|
|||
(list (read-string "Entry: " (format-time-string "%Y/%m/%d "))))
|
||||
(let* ((args (mapcar 'shell-quote-argument (split-string entry)))
|
||||
(date (car args))
|
||||
exit-code)
|
||||
(insert-year t) exit-code)
|
||||
(if (string-match "\\([0-9]+\\)/\\([0-9]+\\)/\\([0-9]+\\)" 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 1 date)))))
|
||||
(ledger-find-slot date)
|
||||
(save-excursion
|
||||
(if (re-search-backward "^Y " nil t)
|
||||
(setq insert-year nil)))
|
||||
(save-excursion
|
||||
(insert
|
||||
(with-temp-buffer
|
||||
|
|
@ -99,8 +109,11 @@
|
|||
(apply 'call-process ledger-binary-path nil t nil
|
||||
(cons "entry" args)))
|
||||
(if (= 0 exit-code)
|
||||
(buffer-substring (+ (point-min) 5) (point-max))
|
||||
(concat (substring entry 5) "\n\n")))))))
|
||||
(if insert-year
|
||||
(buffer-string)
|
||||
(buffer-substring 5 (point-max)))
|
||||
(concat (if insert-year entry
|
||||
(substring entry 5)) "\n\n")))))))
|
||||
|
||||
(defun ledger-expand-entry ()
|
||||
(interactive)
|
||||
|
|
@ -124,13 +137,23 @@
|
|||
(setq clear t))))
|
||||
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"
|
||||
"A mode for editing ledger data files."
|
||||
(setq comment-start ";" comment-end nil
|
||||
indent-tabs-mode nil)
|
||||
(set (make-local-variable 'comment-start) ";")
|
||||
(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)))
|
||||
(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 ?p)] 'ledger-print-result)
|
||||
(define-key map [(control ?c) (control ?r)] 'ledger-reconcile)))
|
||||
|
||||
(defun ledger-parse-entries (account &optional all-p after-date)
|
||||
|
|
@ -144,12 +167,15 @@
|
|||
(setq total 0.0)
|
||||
(while (looking-at
|
||||
(concat "\\s-+\\([A-Za-z_].+?\\)\\(\\s-*$\\| \\s-*"
|
||||
"\\([^0-9]+\\)\\s-*\\([0-9.]+\\)\\)"))
|
||||
"\\([^0-9]+\\)\\s-*\\([0-9,.]+\\)\\)?"
|
||||
"\\(\\s-+;.+\\)?$"))
|
||||
(let ((acct (match-string 1))
|
||||
(amt (match-string 4)))
|
||||
(if amt
|
||||
(setq amt (string-to-number amt)
|
||||
total (+ total amt)))
|
||||
(when amt
|
||||
(while (string-match "," amt)
|
||||
(setq amt (replace-match "" nil nil amt)))
|
||||
(setq amt (string-to-number amt)
|
||||
total (+ total amt)))
|
||||
(if (string= account acct)
|
||||
(setq entries
|
||||
(cons (list (copy-marker start)
|
||||
|
|
@ -164,6 +190,11 @@
|
|||
"A mode for reconciling ledger entries."
|
||||
(let ((map (make-sparse-keymap)))
|
||||
(define-key map [? ] 'ledger-reconcile-toggle)
|
||||
(define-key map [?q]
|
||||
(function
|
||||
(lambda ()
|
||||
(interactive)
|
||||
(kill-buffer (current-buffer)))))
|
||||
(use-local-map map)))
|
||||
|
||||
(add-to-list 'minor-mode-alist
|
||||
|
|
@ -172,6 +203,23 @@
|
|||
(defvar ledger-buf 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 ()
|
||||
(interactive)
|
||||
(let ((where (get-text-property (point) 'where))
|
||||
|
|
@ -188,44 +236,59 @@
|
|||
(remove-text-properties (line-beginning-position)
|
||||
(line-end-position)
|
||||
(list 'face)))
|
||||
(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))
|
||||
"]")))))))
|
||||
(forward-line)
|
||||
(ledger-update-balance-display)))
|
||||
|
||||
(defun ledger-reconcile (account)
|
||||
(interactive "sAccount to reconcile: ")
|
||||
(defun ledger-reconcile (account &optional days)
|
||||
(interactive "sAccount to reconcile: \nnBack how far (default 30 days): ")
|
||||
(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
|
||||
(goto-char (point-min))
|
||||
(ledger-parse-entries account t then)))
|
||||
(buf (current-buffer)))
|
||||
(pop-to-buffer (generate-new-buffer "*Reconcile*"))
|
||||
(ledger-reconcile-mode)
|
||||
(set (make-local-variable 'ledger-buf) buf)
|
||||
(set (make-local-variable 'ledger-acct) account)
|
||||
(dolist (item items)
|
||||
(let ((beg (point)))
|
||||
(insert (format "%s %-30s %8.2f\n"
|
||||
(format-time-string "%Y/%m/%d" (nth 2 item))
|
||||
(nth 3 item) (nth 4 item)))
|
||||
(if (nth 1 item)
|
||||
(with-current-buffer
|
||||
(pop-to-buffer (generate-new-buffer "*Reconcile*"))
|
||||
(ledger-reconcile-mode)
|
||||
(set (make-local-variable 'ledger-buf) buf)
|
||||
(set (make-local-variable 'ledger-acct) account)
|
||||
(ledger-update-balance-display)
|
||||
(dolist (item items)
|
||||
(let ((beg (point)))
|
||||
(insert (format "%s %-30s %8.2f\n"
|
||||
(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))
|
||||
(list 'face 'bold
|
||||
'where (nth 0 item)))
|
||||
(set-text-properties beg (1- (point))
|
||||
(list 'where (nth 0 item)))))
|
||||
(goto-char (point-min)))))
|
||||
(list 'where (nth 0 item)))))
|
||||
(goto-char (point-min))))))
|
||||
|
||||
(defun ledger-align-dollars (&optional column)
|
||||
(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)
|
||||
|
||||
|
|
|
|||
66
ledger.h
66
ledger.h
|
|
@ -1,5 +1,5 @@
|
|||
#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 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 bool has_price() const = 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_negative() const = 0;
|
||||
virtual int compare(const amount * other) const = 0;
|
||||
|
||||
// Assignment
|
||||
|
||||
|
|
@ -115,15 +117,9 @@ class mask
|
|||
bool match(const std::string& str) const;
|
||||
};
|
||||
|
||||
typedef std::list<mask> regexps_map;
|
||||
typedef std::list<mask>::iterator regexps_map_iterator;
|
||||
typedef std::list<mask>::const_iterator regexps_map_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);
|
||||
|
||||
typedef std::list<mask> regexps_list;
|
||||
typedef std::list<mask>::iterator regexps_list_iterator;
|
||||
typedef std::list<mask>::const_iterator regexps_list_const_iterator;
|
||||
|
||||
class account;
|
||||
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 finalize(bool do_compute = false);
|
||||
|
||||
|
|
@ -223,7 +219,10 @@ class totals
|
|||
void credit(const amount * val);
|
||||
void credit(const totals& other);
|
||||
|
||||
void negate();
|
||||
|
||||
bool is_zero() const;
|
||||
bool is_negative() const;
|
||||
|
||||
void print(std::ostream& out, int width) const;
|
||||
};
|
||||
|
|
@ -238,15 +237,15 @@ class account
|
|||
account(const account&);
|
||||
|
||||
public:
|
||||
account * parent;
|
||||
account * parent;
|
||||
|
||||
std::string name;
|
||||
std::string name;
|
||||
#ifdef READ_GNUCASH
|
||||
commodity * comm; // default commodity for this account
|
||||
commodity * comm; // default commodity for this account
|
||||
#endif
|
||||
totals balance; // optional, parse-time computed balance
|
||||
int checked; // 'balance' uses this for speed's sake
|
||||
accounts_map children;
|
||||
totals balance; // optional, parse-time computed balance
|
||||
int checked; // 'balance' uses this for speed's sake
|
||||
accounts_map children;
|
||||
|
||||
mutable std::string full_name;
|
||||
|
||||
|
|
@ -258,7 +257,7 @@ class 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&);
|
||||
|
||||
public:
|
||||
typedef std::map<regexps_map *,
|
||||
typedef std::map<regexps_list *,
|
||||
std::list<transaction *> *> virtual_map;
|
||||
|
||||
typedef std::pair<regexps_map *,
|
||||
typedef std::pair<regexps_list *,
|
||||
std::list<transaction *> *> virtual_map_pair;
|
||||
|
||||
typedef virtual_map::const_iterator virtual_map_iterator;
|
||||
|
|
@ -289,7 +288,8 @@ class book
|
|||
void sort(Compare 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 * 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
|
||||
|
||||
extern book * parse_ledger(std::istream& in, regexps_map& regexps,
|
||||
bool compute_balances);
|
||||
extern int parse_ledger(book * ledger, std::istream& in,
|
||||
regexps_list& regexps,
|
||||
bool compute_balances = false,
|
||||
const char * acct_prefix = NULL);
|
||||
#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
|
||||
|
||||
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,
|
||||
struct std::tm * result);
|
||||
extern bool parse_date(const char * date_str, std::time_t * result,
|
||||
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);
|
||||
|
||||
} // 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 <cctype>
|
||||
|
||||
#define TIMELOG_SUPPORT 1
|
||||
|
||||
namespace ledger {
|
||||
|
||||
static inline char * skip_ws(char * ptr)
|
||||
|
|
@ -104,13 +106,11 @@ void parse_price_setting(const std::string& setting)
|
|||
*p++ = '\0';
|
||||
|
||||
commodity * comm = NULL;
|
||||
|
||||
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);
|
||||
} else {
|
||||
else
|
||||
comm = (*item).second;
|
||||
}
|
||||
|
||||
assert(comm);
|
||||
comm->price = create_amount(p);
|
||||
|
|
@ -119,26 +119,20 @@ void parse_price_setting(const std::string& setting)
|
|||
|
||||
#define MAX_LINE 1024
|
||||
|
||||
int linenum;
|
||||
int linenum;
|
||||
static bool do_compute;
|
||||
static std::string account_prefix;
|
||||
|
||||
static bool do_compute;
|
||||
|
||||
transaction * parse_transaction(std::istream& in, book * ledger)
|
||||
transaction * parse_transaction_text(char * line, book * ledger)
|
||||
{
|
||||
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,
|
||||
// and return a pointer to the beginning of the amount. Once
|
||||
// we know where the amount is, we can strip off any
|
||||
// transaction note, and parse it.
|
||||
|
||||
char * p = skip_ws(line);
|
||||
char * cost_str = next_element(p, true);
|
||||
char * note_str;
|
||||
|
||||
|
|
@ -181,7 +175,8 @@ transaction * parse_transaction(std::istream& in, book * ledger)
|
|||
*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)
|
||||
xact->acct->balance.credit(xact->cost);
|
||||
|
|
@ -189,6 +184,15 @@ transaction * parse_transaction(std::istream& in, book * ledger)
|
|||
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 * curr = new entry(ledger);
|
||||
|
|
@ -247,7 +251,7 @@ void parse_automated_transactions(std::istream& in, book * ledger)
|
|||
{
|
||||
static char line[MAX_LINE + 1];
|
||||
|
||||
regexps_map * masks = NULL;
|
||||
regexps_list * masks = NULL;
|
||||
|
||||
while (! in.eof() && in.peek() == '=') {
|
||||
in.getline(line, MAX_LINE);
|
||||
|
|
@ -257,7 +261,7 @@ void parse_automated_transactions(std::istream& in, book * ledger)
|
|||
p = skip_ws(p);
|
||||
|
||||
if (! masks)
|
||||
masks = new regexps_map;
|
||||
masks = new regexps_list;
|
||||
masks->push_back(mask(p));
|
||||
}
|
||||
|
||||
|
|
@ -291,22 +295,32 @@ void parse_automated_transactions(std::istream& in, book * ledger)
|
|||
// Ledger parser
|
||||
//
|
||||
|
||||
book * parse_ledger(std::istream& in, regexps_map& regexps,
|
||||
bool compute_balances)
|
||||
#ifdef TIMELOG_SUPPORT
|
||||
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];
|
||||
char c;
|
||||
static char line[MAX_LINE + 1];
|
||||
char c;
|
||||
int count = 0;
|
||||
std::string old_account_prefix = account_prefix;
|
||||
|
||||
book * ledger = new book;
|
||||
|
||||
main_ledger = ledger;
|
||||
do_compute = compute_balances;
|
||||
linenum = 0;
|
||||
linenum = 1;
|
||||
do_compute = compute_balances;
|
||||
if (acct_prefix) {
|
||||
account_prefix += acct_prefix;
|
||||
account_prefix += ":";
|
||||
}
|
||||
|
||||
while (! in.eof()) {
|
||||
switch (in.peek()) {
|
||||
case -1: // end of file
|
||||
return ledger;
|
||||
goto done;
|
||||
|
||||
case '\n':
|
||||
linenum++;
|
||||
|
|
@ -314,11 +328,99 @@ book * parse_ledger(std::istream& in, regexps_map& regexps,
|
|||
in.get(c);
|
||||
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
|
||||
in >> c;
|
||||
in >> ledger->current_year;
|
||||
break;
|
||||
|
||||
#ifdef TIMELOG_SUPPORT
|
||||
case 'h':
|
||||
case 'b':
|
||||
#endif
|
||||
case ';': // a comment line
|
||||
in.getline(line, MAX_LINE);
|
||||
linenum++;
|
||||
|
|
@ -339,13 +441,89 @@ book * parse_ledger(std::istream& in, regexps_map& regexps,
|
|||
do_compute = compute_balances;
|
||||
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:
|
||||
if (entry * ent = parse_entry(in, ledger))
|
||||
if (entry * ent = parse_entry(in, ledger)) {
|
||||
ledger->entries.push_back(ent);
|
||||
count++;
|
||||
}
|
||||
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
|
||||
|
|
|
|||
597
reports.cc
597
reports.cc
|
|
@ -1,13 +1,15 @@
|
|||
#include "ledger.h"
|
||||
|
||||
#define LEDGER_VERSION "1.3"
|
||||
#define LEDGER_VERSION "1.6"
|
||||
|
||||
#include <fstream>
|
||||
#include <cstring>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace ledger {
|
||||
|
||||
static bool cleared_only = false;
|
||||
static bool uncleared_only = false;
|
||||
static bool cost_basis = false;
|
||||
static bool show_virtual = true;
|
||||
static bool get_quotes = false;
|
||||
static bool show_children = false;
|
||||
|
|
@ -15,6 +17,12 @@ static bool show_sorted = false;
|
|||
static bool show_empty = false;
|
||||
static bool show_subtotals = true;
|
||||
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 bool have_beginning = false;
|
||||
|
|
@ -65,39 +73,156 @@ static bool matches_date_range(entry * ent)
|
|||
// Balance reporting code
|
||||
//
|
||||
|
||||
static void display_total(std::ostream& out, totals& balance,
|
||||
account * acct, bool top_level,
|
||||
int * headlines)
|
||||
static bool satisfies_limit(totals& balance)
|
||||
{
|
||||
bool displayed = false;
|
||||
bool satisfies = true;
|
||||
bool invert = false;
|
||||
|
||||
if (acct->checked == 1 &&
|
||||
(show_empty || ! acct->balance.is_zero())) {
|
||||
displayed = true;
|
||||
assert(lower_limit);
|
||||
|
||||
acct->balance.print(out, 20);
|
||||
if (show_subtotals && top_level)
|
||||
balance.credit(acct->balance);
|
||||
if (balance.is_negative())
|
||||
invert = true;
|
||||
else
|
||||
lower_limit->negate();
|
||||
|
||||
if (acct->parent && ! full_names && ! top_level) {
|
||||
for (const account * a = acct; a; a = a->parent)
|
||||
out << " ";
|
||||
out << acct->name << std::endl;
|
||||
} else {
|
||||
out << " " << acct->as_str() << std::endl;
|
||||
(*headlines)++;
|
||||
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 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
|
||||
|
||||
for (accounts_map_iterator i = acct->children.begin();
|
||||
i != acct->children.end();
|
||||
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
|
||||
// totals
|
||||
|
|
@ -105,7 +230,8 @@ void report_balances(std::ostream& out, regexps_map& regexps)
|
|||
for (entries_list_iterator i = main_ledger->entries.begin();
|
||||
i != main_ledger->entries.end();
|
||||
i++) {
|
||||
if ((cleared_only && ! (*i)->cleared) || ! matches_date_range(*i))
|
||||
if ((cleared_only && ! (*i)->cleared) ||
|
||||
(uncleared_only && (*i)->cleared) || ! matches_date_range(*i))
|
||||
continue;
|
||||
|
||||
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;
|
||||
acct;
|
||||
acct = show_subtotals ? acct->parent : NULL) {
|
||||
bool by_exclusion = false;
|
||||
bool match = false;
|
||||
|
||||
if (acct->checked == 0) {
|
||||
if (regexps.empty()) {
|
||||
if (! (show_children || ! acct->parent))
|
||||
acct->checked = 2;
|
||||
else
|
||||
acct->checked = 1;
|
||||
}
|
||||
else {
|
||||
bool by_exclusion = false;
|
||||
bool match = matches(regexps, acct->as_str(), &by_exclusion);
|
||||
} else {
|
||||
match = matches(regexps, acct->as_str(), &by_exclusion);
|
||||
if (! match) {
|
||||
acct->checked = 2;
|
||||
}
|
||||
|
|
@ -144,9 +271,26 @@ void report_balances(std::ostream& out, regexps_map& regexps)
|
|||
|
||||
if (acct->checked == 1) {
|
||||
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);
|
||||
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();
|
||||
i != main_ledger->accounts.end();
|
||||
i++)
|
||||
display_total(out, balance, (*i).second, true, &headlines);
|
||||
i++) {
|
||||
adjust_total((*i).second);
|
||||
display_total(out, balance, (*i).second, 0, &headlines);
|
||||
}
|
||||
|
||||
// Print the total of all the balances shown
|
||||
|
||||
if (show_subtotals && headlines > 1) {
|
||||
if (show_subtotals && headlines > 1 && ! balance.is_zero()) {
|
||||
out << "--------------------" << std::endl;
|
||||
balance.print(out, 20);
|
||||
out << std::endl;
|
||||
|
|
@ -188,98 +334,183 @@ static std::string truncated(const std::string& str, int width)
|
|||
return buf;
|
||||
}
|
||||
|
||||
void print_register(const std::string& acct_name, std::ostream& out,
|
||||
regexps_map& regexps)
|
||||
enum periodicity_t {
|
||||
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);
|
||||
|
||||
// Walk through all of the ledger entries, printing their register
|
||||
// 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();
|
||||
i != main_ledger->entries.end();
|
||||
i++) {
|
||||
if ((cleared_only && ! (*i)->cleared) ||
|
||||
(uncleared_only && (*i)->cleared) ||
|
||||
! matches_date_range(*i) || ! (*i)->matches(regexps))
|
||||
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();
|
||||
x != (*i)->xacts.end();
|
||||
x++) {
|
||||
if (! acct_regex.match((*x)->acct->as_str()))
|
||||
if (! acct_regex.match((*x)->acct->as_str()) ||
|
||||
(lower_limit && ! satisfies_limit((*x)->cost)))
|
||||
continue;
|
||||
|
||||
char buf[32];
|
||||
std::strftime(buf, 31, "%m/%d ", std::localtime(&(*i)->date));
|
||||
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();
|
||||
if (period == PERIOD_NONE) {
|
||||
print_register_transaction(out, *i, *x, balance);
|
||||
} else {
|
||||
xact = *x;
|
||||
}
|
||||
std::string xact_str = xact->acct_as_str();
|
||||
amount * street = (*x)->cost->street(get_quotes);
|
||||
balance.credit(street);
|
||||
|
||||
if (xact == *x && ! show_subtotals)
|
||||
xact_str = "<Splits...>";
|
||||
if (period_sum) {
|
||||
period_sum->credit(street);
|
||||
delete street;
|
||||
} else {
|
||||
period_sum = street;
|
||||
}
|
||||
|
||||
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 || 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;
|
||||
last_acct = (*x)->acct;
|
||||
last_date = (*i)->date;
|
||||
last_mon = entry_mon;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
//
|
||||
|
||||
static void equity_entry(account * acct, regexps_map& regexps,
|
||||
static void equity_entry(account * acct, regexps_list& regexps,
|
||||
std::ostream& out)
|
||||
{
|
||||
if (! acct->balance.is_zero() &&
|
||||
(regexps.empty() || matches(regexps, acct->as_str()))) {
|
||||
entry opening(main_ledger);
|
||||
|
||||
opening.date = std::time(NULL);
|
||||
opening.date = have_ending ? end_date : std::time(NULL);
|
||||
opening.cleared = true;
|
||||
opening.desc = "Opening Balance";
|
||||
|
||||
|
|
@ -330,7 +561,7 @@ static void equity_entry(account * acct, regexps_map& regexps,
|
|||
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
|
||||
// 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)
|
||||
{
|
||||
regexps_map regexps;
|
||||
entry added(main_ledger);
|
||||
entry * matching = NULL;
|
||||
regexps_list regexps;
|
||||
entry added(main_ledger);
|
||||
entry * matching = NULL;
|
||||
|
||||
assert(index < argc);
|
||||
|
||||
|
|
@ -423,14 +654,17 @@ void add_new_entry(int index, int argc, char **argv)
|
|||
|
||||
account * acct = NULL;
|
||||
commodity * cmdty = NULL;
|
||||
for (std::list<transaction *>::iterator x = matching->xacts.begin();
|
||||
x != matching->xacts.end();
|
||||
x++) {
|
||||
if (acct_regex.match((*x)->acct->as_str())) {
|
||||
acct = (*x)->acct;
|
||||
cmdty = (*x)->cost->commdty();
|
||||
break;
|
||||
}
|
||||
|
||||
if (matching) {
|
||||
for (std::list<transaction *>::iterator x = matching->xacts.begin();
|
||||
x != matching->xacts.end();
|
||||
x++) {
|
||||
if (acct_regex.match((*x)->acct->as_str())) {
|
||||
acct = (*x)->acct;
|
||||
cmdty = (*x)->cost->commdty();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (acct)
|
||||
|
|
@ -466,7 +700,12 @@ void add_new_entry(int index, int argc, char **argv)
|
|||
}
|
||||
} else {
|
||||
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;
|
||||
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,
|
||||
// in order to access the static globals above.
|
||||
|
||||
void book::print(std::ostream& out, regexps_map& regexps,
|
||||
bool shortcut) const
|
||||
void book::print(std::ostream& out, regexps_list& regexps,
|
||||
bool shortcut) const
|
||||
{
|
||||
for (entries_list_const_iterator i = entries.begin();
|
||||
i != entries.end();
|
||||
|
|
@ -506,25 +745,33 @@ static void show_help(std::ostream& out)
|
|||
<< " -b DATE specify a beginning date" << std::endl
|
||||
<< " -e DATE specify an ending date" << 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
|
||||
<< " -E also show accounts with zero totals" << std::endl
|
||||
<< " -f FILE specify pathname of ledger data file" << std::endl
|
||||
<< " -F print each account's full name" << std::endl
|
||||
<< " -h display this help text" << 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 download price quotes from the Internet" << std::endl
|
||||
<< " (works by running the command \"getquote SYMBOL\")" << std::endl
|
||||
<< " -R do not factor in virtual transactions" << 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
|
||||
<< "commands:" << std::endl
|
||||
<< " balance show balance totals" << std::endl
|
||||
<< " register display a register for ACCOUNT" << 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[])
|
||||
{
|
||||
std::istream * file = NULL;
|
||||
std::string prices;
|
||||
regexps_map regexps;
|
||||
int index;
|
||||
int index;
|
||||
std::string prices;
|
||||
std::string limit;
|
||||
regexps_list regexps;
|
||||
|
||||
std::vector<std::string> files;
|
||||
|
||||
// Parse the command-line options
|
||||
|
||||
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)) {
|
||||
case 'b':
|
||||
have_beginning = true;
|
||||
|
|
@ -574,19 +824,27 @@ int main(int argc, char * argv[])
|
|||
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 'U': uncleared_only = true; break;
|
||||
case 'B': cost_basis = true; break;
|
||||
case 'R': show_virtual = false; break;
|
||||
case 's': show_children = true; break;
|
||||
case 'S': show_sorted = true; break;
|
||||
case 'E': show_empty = true; break;
|
||||
case 'n': show_subtotals = false; 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
|
||||
case 'i':
|
||||
read_regexps(optarg, regexps);
|
||||
if (access(optarg, R_OK) != -1)
|
||||
read_regexps(optarg, regexps);
|
||||
break;
|
||||
|
||||
// -p "COMMODITY=PRICE"
|
||||
|
|
@ -599,6 +857,10 @@ int main(int argc, char * argv[])
|
|||
get_quotes = true;
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
limit = optarg;
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
std::cout
|
||||
<< "Ledger Accouting Tool " LEDGER_VERSION << std::endl
|
||||
|
|
@ -620,20 +882,6 @@ int main(int argc, char * argv[])
|
|||
|
||||
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
|
||||
|
||||
const std::string command = argv[index++];
|
||||
|
|
@ -655,49 +903,64 @@ int main(int argc, char * argv[])
|
|||
for (; index < argc; index++)
|
||||
regexps.push_back(mask(argv[index]));
|
||||
|
||||
// Parse the ledger
|
||||
// A ledger data file must be specified
|
||||
|
||||
#ifdef READ_GNUCASH
|
||||
char buf[32];
|
||||
file->get(buf, 31);
|
||||
file->seekg(0);
|
||||
int entry_count = 0;
|
||||
|
||||
main_ledger = new book;
|
||||
|
||||
if (std::strncmp(buf, "<?xml version=\"1.0\"?>", 21) == 0)
|
||||
main_ledger = parse_gnucash(*file, command == "equity");
|
||||
else
|
||||
#endif
|
||||
main_ledger = parse_ledger(*file, regexps, command == "equity");
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
delete file;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Record any prices specified by the user
|
||||
|
||||
if (! prices.empty()) {
|
||||
if (access(prices.c_str(), R_OK) != -1) {
|
||||
std::ifstream pricedb(prices.c_str());
|
||||
while (! pricedb.eof()) {
|
||||
char buf[80];
|
||||
pricedb.getline(buf, 79);
|
||||
if (*buf && ! std::isspace(*buf))
|
||||
parse_price_setting(buf);
|
||||
}
|
||||
} else {
|
||||
if (access(prices.c_str(), R_OK) != -1)
|
||||
read_prices(prices);
|
||||
else
|
||||
parse_price_setting(prices);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the lower limit, if specified
|
||||
|
||||
if (! limit.empty())
|
||||
lower_limit = create_amount(limit);
|
||||
|
||||
// Process the command
|
||||
|
||||
if (command == "balance" || command == "bal") {
|
||||
report_balances(std::cout, regexps);
|
||||
}
|
||||
else if (command == "register" || command == "reg") {
|
||||
if (show_sorted)
|
||||
if (show_sorted || print_monthly)
|
||||
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") {
|
||||
if (show_sorted)
|
||||
|
|
@ -721,6 +984,12 @@ int main(int argc, char * argv[])
|
|||
// process is about to give back its heap to the OS.
|
||||
|
||||
delete main_ledger;
|
||||
|
||||
if (lower_limit)
|
||||
delete lower_limit;
|
||||
|
||||
if (negonly_regexp)
|
||||
delete negonly_regexp;
|
||||
#endif
|
||||
|
||||
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