1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
|
#!/usr/bin/env python
# Ledger, the command-line accounting tool
#
# Copyright (c) 2003-2004, New Artisans LLC. All rights reserved.
#
# This program is made available under the terms of the BSD Public
# License. See the LICENSE file included with the distribution for
# details and disclaimer.
#
# This script provides a Python front-end to the ledger library, and
# replicates the functionality of the C++ front-end, main.cc. It is
# provided as an example, and as a starting point for creating custom
# front-ends based on the Ledger module. See the documentation for an
# API reference, and how to use this module.
import os
import sys
import string
from ledger import *
# Create the main journal object, into which all entries will be
# recorded. Once done, the 'journal' may be iterated to yield those
# entries, in the same order as which they appeared in the journal
# file.
journal = Journal ()
# This call registers all of the default command-line options that
# Ledger supports into the option handling mechanism. Skip this call
# if you wish to do all of your own processing -- in which case simply
# modify the 'config' object however you like.
add_config_option_handlers ()
# Process the command-line arguments, test whether caching should be
# enabled, and then process any option settings from the execution
# environment. Some historical environment variable names are also
# supported.
args = process_arguments (sys.argv[1:])
config.use_cache = not config.data_file
process_environment (os.environ, "LEDGER_")
if os.environ.has_key ("LEDGER"):
process_option ("file", os.environ["LEDGER"])
if os.environ.has_key ("PRICE_HIST"):
process_option ("price-db", os.environ["PRICE_HIST"])
if os.environ.has_key ("PRICE_EXP"):
process_option ("price-exp", os.environ["PRICE_EXP"])
# If no argument remain, then no command word was given. Report the
# default help text and exit.
if len (args) == 0:
option_help ()
sys.exit (0)
# The command word is in the first argument. Canonicalize it to a
# unique, simple form that the remaining code can use to find out
# which command was specified.
command = args.pop (0);
if command == "balance" or command == "bal" or command == "b":
command = "b"
elif command == "register" or command == "reg" or command == "r":
command = "r"
elif command == "print" or command == "p":
command = "p"
elif command == "entry":
command = "e"
elif command == "equity":
command = "E"
else:
print "Unrecognized command:", command
sys.exit (1)
# Create all the parser objects to be used. They are all registered,
# so that Ledger will try each one in turn whenever it is presented
# with a data file. They are attempted in reverse order to their
# registry. Note that Gnucash parsing is only available if the Ledger
# module was built with such support (which requires the xmlparse C
# library).
text_parser = TextualParser ()
bin_parser = BinaryParser ()
qif_parser = QifParser ()
gnucash_parser = None
try:
gnucash_parser = GnucashParser ()
except:
pass
register_parser (text_parser)
register_parser (bin_parser)
if gnucash_parser:
register_parser (gnucash_parser)
register_parser (qif_parser)
# Parse all entries from the user specified locations (found in
# 'config') into the journal object we created. The two parsers given
# as explicit arguments indicate: the parser to be used for standard
# input, and the parser to be used for cache files.
parse_ledger_data (journal, text_parser, bin_parser)
# Now that everything has been correctly parsed (parse_ledger_data
# would have thrown an exception if not), we can take time to further
# process the configuration options. This changes the configuration a
# bit based on previous option settings, the command word, and the
# remaining arguments.
config.process_options(command, args);
# If the command is "e", use the method journal.derive_entry to create
# a brand new entry based on the arguments given.
new_entry = None
if command == "e":
new_entry = derive_new_entry (journal, args)
if new_entry is None:
sys.exit (1)
# Determine the format string to used, based on the command.
if config.format_string:
format = config.format_string
elif command == "b":
format = config.balance_format
elif command == "r":
format = config.register_format
elif command == "E":
format = config.equity_format
else:
format = config.print_format
# The following two classes are responsible for outputing transactions
# and accounts to the user. There are corresponding C++ versions to
# these, but they rely on I/O streams, which Boost.Python does not
# provide a conversion layer for.
class FormatTransaction (TransactionHandler):
last_entry = None
output = None
def __init__ (self, fmt):
try:
i = string.index (fmt, '%/')
self.formatter = Format (fmt[: i])
self.nformatter = Format (fmt[i + 2 :])
except ValueError:
self.formatter = Format (fmt)
self.nformatter = None
self.last_entry = None
if config.output_file:
self.output = open(config.output_file, "w")
else:
self.output = sys.stdout
TransactionHandler.__init__ (self)
def __del__ (self):
if config.output_file:
self.output.close ()
def flush (self):
self.output.flush ()
def __call__ (self, xact):
if not transaction_has_xdata (xact) or \
not transaction_xdata (xact).dflags & TRANSACTION_DISPLAYED:
if self.nformatter is not None and \
self.last_entry is not None and \
xact.entry == self.last_entry:
self.output.write(self.nformatter.format(xact))
else:
self.output.write(self.formatter.format(xact))
self.last_entry = xact.entry
class FormatAccount (AccountHandler):
output = None
def __init__ (self, fmt, pred):
self.formatter = Format (fmt)
self.predicate = AccountPredicate (pred)
if config.output_file:
self.output = open(config.output_file, "w")
else:
self.output = sys.stdout
AccountHandler.__init__ (self)
def __del__ (self):
if config.output_file:
self.output.close ()
def flush (self):
self.output.flush ()
def __call__ (self, account):
if display_account (account, self.predicate):
if not account.parent:
account_xdata (account).dflags |= ACCOUNT_TO_DISPLAY
else:
self.output.write(self.formatter.format(account))
account_xdata (account).dflags |= ACCOUNT_DISPLAYED
# Set the final transaction handler: for balances and equity reports,
# it will simply add the value of the transaction to the account's
# xdata, which is used a bit later to report those totals. For all
# other reports, the transaction data is sent to the configured output
# location (default is sys.stdout).
if command == "b" or command == "E":
handler = SetAccountValue()
else:
handler = FormatTransaction(format)
# Chain transaction filters on top of the base handler. Most of these
# filters customize the output for reporting. None of this is done
# for balance or equity reports, which don't need it.
if not (command == "b" or command == "E"):
if config.display_predicate:
handler = FilterTransactions(handler, config.display_predicate)
handler = CalcTransactions(handler, config.show_inverted)
if config.sort_string:
handler = SortTransactions(handler, config.sort_string)
if config.show_revalued:
handler = ChangedValueTransactions(handler, config.show_revalued_only)
if config.show_collapsed:
handler = CollapseTransactions(handler);
if config.show_subtotal:
handler = SubtotalTransactions(handler)
elif config.report_interval:
handler = IntervalTransactions(handler, config.report_interval)
handler = SortTransactions(handler, "d")
elif config.days_of_the_week:
handler = DowTransactions(handler)
# The next two transaction filters are used by all reports.
if config.show_related:
handler = RelatedTransactions(handler, config.show_all_related)
if config.predicate:
handler = FilterTransactions(handler, config.predicate)
# Walk the journal's entries, and pass each entry's transaction to the
# handler chain established above. And although a journal's entries
# can be walked using Python, it is significantly faster to do this
# simple walk in C++, using `walk_entries'.
if 1:
walk_entries (journal, handler)
else:
for entry in journal:
for xact in entry:
handler (xact)
# Flush the handlers, causing them to output whatever data is still
# pending.
handler.flush ()
# For the balance and equity reports, the account totals now need to
# be displayed. This is different from outputting transactions, in
# that we are now outputting account totals to display a summary of
# the transactions that were just walked.
if command == "b":
acct_formatter = FormatAccount (format, config.display_predicate)
sum_accounts (journal.master)
walk_accounts (journal.master, acct_formatter, config.sort_string)
acct_formatter.flush ()
#if account_has_xdata(journal.master):
# account_xdata(journal.master).value = account_xdata(journal.master).total;
#
# if account_xdata(journal.master).dflags & ACCOUNT_TO_DISPLAY:
# print "--------------------"
# config.format.format(out, details_t(*journal->master));
#elif command == "E":
# format_equity acct_formatter(out, config.format, config.nformat,
# config.display_predicate);
# sum_accounts(*journal->master);
# walk_accounts(*journal->master, acct_formatter, config.sort_string);
# acct_formatter.flush();
# If the cache is being used, and is dirty, update it now.
if config.use_cache and config.cache_dirty and config.cache_file:
write_binary_journal(config.cache_file, journal);
# We're done!
|