*** empty log message ***
This commit is contained in:
parent
bfff951c31
commit
3cfae27947
11 changed files with 421 additions and 288 deletions
2
Makefile
2
Makefile
|
|
@ -43,7 +43,7 @@ ledger.info: ledger.texi
|
|||
g++ $(CFLAGS) $(INCS) $(DFLAGS) -c -o $@ $<
|
||||
|
||||
clean:
|
||||
rm -f ledger *.o
|
||||
rm -f ledger ledger.info *.o *~ .\#*
|
||||
|
||||
rebuild: clean deps all
|
||||
|
||||
|
|
|
|||
57
amount.cc
57
amount.cc
|
|
@ -1,20 +1,24 @@
|
|||
#include <sstream>
|
||||
|
||||
#include <gmp.h> // GNU multi-precision library
|
||||
|
||||
#include "ledger.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <gmp.h> // GNU multi-precision library
|
||||
|
||||
namespace ledger {
|
||||
|
||||
#define MAX_PRECISION 10 // must be 2 or higher
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// The `amount' structure. Every transaction has an associated amount,
|
||||
// which is represented by this structure. `amount' uses the GNU
|
||||
// multi-precision library, allowing for arbitrarily large amounts.
|
||||
// Each amount is a quantity of commodity at a certain price; the
|
||||
// default commodity is the US dollar, with a price of 1.00.
|
||||
// The `amount' structure. Every transaction has an associated
|
||||
// amount, which is represented by this structure. `amount' uses the
|
||||
// GNU multi-precision library, allowing for arbitrarily large
|
||||
// amounts. Each amount is a quantity of a certain commodity, with
|
||||
// an optional price per-unit for that commodity at the time the
|
||||
// amount was stated.
|
||||
//
|
||||
// To create an amount, for example:
|
||||
//
|
||||
// amount * cost = create_amount("50.2 MSFT @ $100.50");
|
||||
//
|
||||
|
||||
class gmp_amount : public amount
|
||||
|
|
@ -57,20 +61,21 @@ class gmp_amount : public amount
|
|||
}
|
||||
virtual void set_value(const amount * val);
|
||||
|
||||
virtual operator bool() const;
|
||||
virtual bool is_zero() const;
|
||||
|
||||
virtual void negate() {
|
||||
mpz_ui_sub(quantity, 0, quantity);
|
||||
}
|
||||
virtual void credit(const amount * other);
|
||||
|
||||
virtual void parse(const char * num);
|
||||
virtual std::string as_str(bool full_prec) const;
|
||||
virtual void parse(const std::string& num);
|
||||
virtual const std::string as_str(bool full_prec) const;
|
||||
|
||||
friend amount * create_amount(const char * value, const amount * cost);
|
||||
friend amount * create_amount(const std::string& value,
|
||||
const amount * cost);
|
||||
};
|
||||
|
||||
amount * create_amount(const char * value, const amount * cost)
|
||||
amount * create_amount(const std::string& value, const amount * cost)
|
||||
{
|
||||
gmp_amount * a = new gmp_amount();
|
||||
a->parse(value);
|
||||
|
|
@ -277,7 +282,7 @@ void gmp_amount::set_value(const amount * val)
|
|||
mpz_clear(addend);
|
||||
}
|
||||
|
||||
gmp_amount::operator bool() const
|
||||
bool gmp_amount::is_zero() const
|
||||
{
|
||||
mpz_t copy;
|
||||
mpz_init_set(copy, quantity);
|
||||
|
|
@ -286,7 +291,7 @@ gmp_amount::operator bool() const
|
|||
round(copy, copy, quantity_comm->precision);
|
||||
bool zero = mpz_sgn(copy) == 0;
|
||||
mpz_clear(copy);
|
||||
return ! zero;
|
||||
return zero;
|
||||
}
|
||||
|
||||
static std::string amount_to_str(const commodity * comm, const mpz_t val,
|
||||
|
|
@ -416,7 +421,7 @@ static std::string amount_to_str(const commodity * comm, const mpz_t val,
|
|||
return s.str();
|
||||
}
|
||||
|
||||
std::string gmp_amount::as_str(bool full_prec) const
|
||||
const std::string gmp_amount::as_str(bool full_prec) const
|
||||
{
|
||||
std::ostringstream s;
|
||||
|
||||
|
|
@ -430,8 +435,11 @@ std::string gmp_amount::as_str(bool full_prec) const
|
|||
return s.str();
|
||||
}
|
||||
|
||||
static void parse_number(mpz_t out, const char * num, commodity * comm)
|
||||
static void parse_number(mpz_t out, const std::string& number,
|
||||
commodity * comm)
|
||||
{
|
||||
const char * num = number.c_str();
|
||||
|
||||
if (char * p = std::strchr(num, '/')) {
|
||||
mpz_t numer;
|
||||
mpz_t val;
|
||||
|
|
@ -559,7 +567,7 @@ static commodity * parse_amount(mpz_t out, const char * num,
|
|||
return comm;
|
||||
}
|
||||
|
||||
void gmp_amount::parse(const char * num)
|
||||
void gmp_amount::parse(const std::string& number)
|
||||
{
|
||||
// Compile the regular expression used for parsing amounts
|
||||
static pcre * re = NULL;
|
||||
|
|
@ -576,17 +584,20 @@ void gmp_amount::parse(const char * num)
|
|||
int ovector[60];
|
||||
int matched;
|
||||
|
||||
matched = pcre_exec(re, NULL, num, std::strlen(num), 0, 0, ovector, 60);
|
||||
matched = pcre_exec(re, NULL, number.c_str(), number.length(),
|
||||
0, 0, ovector, 60);
|
||||
if (matched > 0) {
|
||||
quantity_comm = parse_amount(quantity, num, matched, ovector, 1);
|
||||
quantity_comm = parse_amount(quantity, number.c_str(), matched,
|
||||
ovector, 1);
|
||||
|
||||
// If the following succeeded, then we have a price
|
||||
if (ovector[8 * 2] >= 0) {
|
||||
priced = true;
|
||||
price_comm = parse_amount(price, num, matched, ovector, 9);
|
||||
price_comm = parse_amount(price, number.c_str(), matched,
|
||||
ovector, 9);
|
||||
}
|
||||
} else {
|
||||
std::cerr << "Failed to parse amount: " << num << std::endl;
|
||||
std::cerr << "Failed to parse amount: " << number << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
84
balance.cc
84
balance.cc
|
|
@ -16,51 +16,25 @@ static bool show_empty;
|
|||
static bool no_subtotals;
|
||||
static bool full_names;
|
||||
|
||||
static bool account_matches(const account * acct,
|
||||
const std::list<mask>& regexps,
|
||||
bool * true_match)
|
||||
{
|
||||
bool match = false;
|
||||
*true_match = false;
|
||||
|
||||
if (show_children) {
|
||||
for (const account * a = acct; a; a = a->parent) {
|
||||
bool exclude = false;
|
||||
if (matches(regexps, a->name, &exclude)) {
|
||||
match = true;
|
||||
*true_match = a == acct;
|
||||
break;
|
||||
}
|
||||
if (exclude)
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
match = matches(regexps, acct->as_str());
|
||||
if (match)
|
||||
*true_match = matches(regexps, acct->name);
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
static void display_total(std::ostream& out, totals& balance,
|
||||
account * acct, bool top_level,
|
||||
const std::list<mask>& regexps)
|
||||
account * acct, bool top_level)
|
||||
{
|
||||
bool displayed = false;
|
||||
|
||||
if (acct->checked == 1 && (show_empty || acct->balance)) {
|
||||
if (acct->checked == 1 &&
|
||||
(show_empty || ! acct->balance.is_zero())) {
|
||||
displayed = true;
|
||||
|
||||
out << acct->balance;
|
||||
acct->balance.print(out, 20);
|
||||
if (! no_subtotals && top_level)
|
||||
balance.credit(acct->balance);
|
||||
|
||||
if (acct->parent && ! no_subtotals && ! full_names) {
|
||||
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 << std::endl;
|
||||
out << " " << acct->as_str() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -69,7 +43,7 @@ static void display_total(std::ostream& out, totals& balance,
|
|||
for (accounts_iterator i = acct->children.begin();
|
||||
i != acct->children.end();
|
||||
i++)
|
||||
display_total(out, balance, (*i).second, ! displayed, regexps);
|
||||
display_total(out, balance, (*i).second, ! displayed);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -77,7 +51,8 @@ static void display_total(std::ostream& out, totals& balance,
|
|||
// Balance reporting code
|
||||
//
|
||||
|
||||
void report_balances(int argc, char **argv, std::ostream& out)
|
||||
void report_balances(int argc, char ** argv, regexps_t& regexps,
|
||||
std::ostream& out)
|
||||
{
|
||||
show_children = false;
|
||||
show_empty = false;
|
||||
|
|
@ -120,21 +95,32 @@ void report_balances(int argc, char **argv, std::ostream& out)
|
|||
acct;
|
||||
acct = no_subtotals ? NULL : acct->parent) {
|
||||
if (acct->checked == 0) {
|
||||
bool true_match = false;
|
||||
if (! (regexps.empty() ||
|
||||
account_matches(acct, regexps, &true_match)))
|
||||
if (regexps.empty()) {
|
||||
if (! (show_children || ! acct->parent))
|
||||
acct->checked = 2;
|
||||
else if (! (true_match || show_children || ! acct->parent))
|
||||
acct->checked = 3;
|
||||
else
|
||||
acct->checked = 1;
|
||||
}
|
||||
else {
|
||||
bool by_exclusion;
|
||||
bool match = matches(regexps, acct->as_str(),
|
||||
&by_exclusion);
|
||||
if (! match) {
|
||||
acct->checked = 2;
|
||||
}
|
||||
else if (by_exclusion) {
|
||||
if (! (show_children || ! acct->parent))
|
||||
acct->checked = 2;
|
||||
else
|
||||
acct->checked = 1;
|
||||
}
|
||||
else {
|
||||
acct->checked = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (acct->checked == 2)
|
||||
break;
|
||||
else if (acct->checked == 3)
|
||||
continue;
|
||||
|
||||
if (acct->checked == 1)
|
||||
acct->balance.credit((*x)->cost->street());
|
||||
}
|
||||
}
|
||||
|
|
@ -148,13 +134,15 @@ void report_balances(int argc, char **argv, std::ostream& out)
|
|||
for (accounts_iterator i = main_ledger.accounts.begin();
|
||||
i != main_ledger.accounts.end();
|
||||
i++)
|
||||
display_total(out, balance, (*i).second, true, regexps);
|
||||
display_total(out, balance, (*i).second, true);
|
||||
|
||||
// Print the total of all the balances shown
|
||||
|
||||
if (! no_subtotals && balance)
|
||||
out << "--------------------" << std::endl
|
||||
<< balance << std::endl;
|
||||
if (! no_subtotals && ! balance.is_zero()) {
|
||||
out << "--------------------" << std::endl;
|
||||
balance.print(out, 20);
|
||||
out << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ledger
|
||||
|
|
|
|||
16
equity.cc
16
equity.cc
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
namespace ledger {
|
||||
|
||||
static void equity_entry(std::ostream& out, account * acct,
|
||||
const std::list<mask>& regexps)
|
||||
static void equity_entry(account * acct, regexps_t& regexps,
|
||||
std::ostream& out)
|
||||
{
|
||||
if (acct->balance &&
|
||||
if (! acct->balance.is_zero() &&
|
||||
(regexps.empty() || matches(regexps, acct->as_str()))) {
|
||||
entry opening;
|
||||
|
||||
|
|
@ -17,7 +17,8 @@ static void equity_entry(std::ostream& out, account * acct,
|
|||
for (totals::const_iterator i = acct->balance.amounts.begin();
|
||||
i != acct->balance.amounts.end();
|
||||
i++) {
|
||||
if (! *((*i).second)) // skip if zero balance for the commodity
|
||||
// Skip it, if there is a zero balance for the commodity
|
||||
if ((*i).second->is_zero())
|
||||
continue;
|
||||
|
||||
xact = new transaction();
|
||||
|
|
@ -40,7 +41,7 @@ static void equity_entry(std::ostream& out, account * acct,
|
|||
for (accounts_iterator i = acct->children.begin();
|
||||
i != acct->children.end();
|
||||
i++)
|
||||
equity_entry(out, (*i).second, regexps);
|
||||
equity_entry((*i).second, regexps, out);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -50,7 +51,8 @@ static void equity_entry(std::ostream& out, account * acct,
|
|||
// balances.
|
||||
//
|
||||
|
||||
void equity_ledger(int argc, char **argv, std::ostream& out)
|
||||
void equity_ledger(int argc, char ** argv, regexps_t& regexps,
|
||||
std::ostream& out)
|
||||
{
|
||||
optind = 1;
|
||||
|
||||
|
|
@ -67,7 +69,7 @@ void equity_ledger(int argc, char **argv, std::ostream& out)
|
|||
for (accounts_iterator i = main_ledger.accounts.begin();
|
||||
i != main_ledger.accounts.end();
|
||||
i++)
|
||||
equity_entry(out, (*i).second, regexps);
|
||||
equity_entry((*i).second, regexps, out);
|
||||
}
|
||||
|
||||
} // namespace ledger
|
||||
|
|
|
|||
104
ledger.cc
104
ledger.cc
|
|
@ -34,9 +34,8 @@ void entry::print(std::ostream& out, bool shortcut) const
|
|||
x != xacts.end();
|
||||
x++) {
|
||||
#ifdef HUQUQULLAH
|
||||
if ((*x)->acct->exempt_or_necessary &&
|
||||
(! shortcut || ! ledger::matches(main_ledger.huquq_categories,
|
||||
(*x)->acct->as_str())))
|
||||
if ((*x)->exempt_or_necessary ||
|
||||
(! shortcut && (*x)->acct->exempt_or_necessary))
|
||||
out << " !";
|
||||
else
|
||||
#endif
|
||||
|
|
@ -69,12 +68,12 @@ bool entry::validate(bool show_unaccounted) const
|
|||
if ((*x)->cost)
|
||||
balance.credit((*x)->cost->value());
|
||||
|
||||
if (show_unaccounted && balance) {
|
||||
if (show_unaccounted && ! balance.is_zero()) {
|
||||
std::cerr << "Unaccounted-for balances are:" << std::endl;
|
||||
balance.print(std::cerr, 20);
|
||||
std::cerr << std::endl << std::endl;
|
||||
}
|
||||
return ! balance; // must balance to 0.0
|
||||
return balance.is_zero(); // must balance to 0.0
|
||||
}
|
||||
|
||||
bool entry::matches(const std::list<mask>& regexps) const
|
||||
|
|
@ -117,19 +116,22 @@ void totals::credit(const totals& other)
|
|||
credit((*i).second);
|
||||
}
|
||||
|
||||
totals::operator bool() const
|
||||
bool totals::is_zero() const
|
||||
{
|
||||
for (const_iterator i = amounts.begin(); i != amounts.end(); i++)
|
||||
if (*((*i).second))
|
||||
return true;
|
||||
if (! (*i).second->is_zero())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void totals::print(std::ostream& out, int width) const
|
||||
{
|
||||
bool first = true;
|
||||
for (const_iterator i = amounts.begin(); i != amounts.end(); i++)
|
||||
if (*((*i).second)) {
|
||||
|
||||
for (const_iterator i = amounts.begin(); i != amounts.end(); i++) {
|
||||
if ((*i).second->is_zero())
|
||||
continue;
|
||||
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
|
|
@ -143,7 +145,8 @@ void totals::print(std::ostream& out, int width) const
|
|||
// Print out the entire ledger that was read in, sorted by date.
|
||||
// This can be used to "wash" ugly ledger files.
|
||||
|
||||
void print_ledger(int argc, char *argv[], std::ostream& out)
|
||||
void print_ledger(int argc, char ** argv, regexps_t& regexps,
|
||||
std::ostream& out)
|
||||
{
|
||||
bool use_shortcuts = true;
|
||||
|
||||
|
|
@ -174,37 +177,40 @@ void print_ledger(int argc, char *argv[], std::ostream& out)
|
|||
(*i)->print(out, use_shortcuts);
|
||||
}
|
||||
|
||||
void record_regexp(char * pattern, std::list<mask>& regexps)
|
||||
mask::mask(const std::string& pat) : exclude(false)
|
||||
{
|
||||
bool exclude = false;
|
||||
|
||||
char * pat = pattern;
|
||||
if (*pat == '-') {
|
||||
const char * p = pat.c_str();
|
||||
if (*p == '-') {
|
||||
exclude = true;
|
||||
pat++;
|
||||
while (std::isspace(*pat))
|
||||
pat++;
|
||||
p++;
|
||||
while (std::isspace(*p))
|
||||
p++;
|
||||
}
|
||||
else if (*pat == '+') {
|
||||
pat++;
|
||||
while (std::isspace(*pat))
|
||||
pat++;
|
||||
else if (*p == '+') {
|
||||
p++;
|
||||
while (std::isspace(*p))
|
||||
p++;
|
||||
}
|
||||
pattern = p;
|
||||
|
||||
const char *error;
|
||||
int erroffset;
|
||||
pcre * re = pcre_compile(pat, PCRE_CASELESS, &error, &erroffset, NULL);
|
||||
if (! re)
|
||||
regexp = pcre_compile(pattern.c_str(), PCRE_CASELESS,
|
||||
&error, &erroffset, NULL);
|
||||
if (! regexp)
|
||||
std::cerr << "Warning: Failed to compile regexp: " << pattern
|
||||
<< std::endl;
|
||||
else
|
||||
regexps.push_back(mask(exclude, re));
|
||||
}
|
||||
|
||||
void read_regexps(const char * path, std::list<mask>& regexps)
|
||||
void record_regexp(const std::string& pattern, regexps_t& regexps)
|
||||
{
|
||||
if (access(path, R_OK) != -1) {
|
||||
std::ifstream file(path);
|
||||
regexps.push_back(mask(pattern));
|
||||
}
|
||||
|
||||
void read_regexps(const std::string& path, regexps_t& regexps)
|
||||
{
|
||||
if (access(path.c_str(), R_OK) != -1) {
|
||||
std::ifstream file(path.c_str());
|
||||
|
||||
while (! file.eof()) {
|
||||
char buf[80];
|
||||
|
|
@ -215,14 +221,20 @@ void read_regexps(const char * path, std::list<mask>& regexps)
|
|||
}
|
||||
}
|
||||
|
||||
bool matches(const std::list<mask>& regexps, const std::string& str,
|
||||
bool * exclude)
|
||||
bool matches(const regexps_t& regexps, const std::string& str,
|
||||
bool * by_exclusion)
|
||||
{
|
||||
assert(! regexps.empty());
|
||||
|
||||
// If the first pattern is an exclude, we assume all patterns match
|
||||
// if they don't match the exclude. If the first pattern is an
|
||||
// include, then only accounts matching the include will match.
|
||||
// if they don't match the exclude -- and by_exclusion will be set
|
||||
// to true to reflect this "by default" behavior. But if the first
|
||||
// pattern is an include, only accounts matching the include will
|
||||
// match, and these are a positive match.
|
||||
|
||||
bool match = (*regexps.begin()).exclude;
|
||||
if (match && by_exclusion)
|
||||
*by_exclusion = true;
|
||||
|
||||
for (std::list<mask>::const_iterator r = regexps.begin();
|
||||
r != regexps.end();
|
||||
|
|
@ -230,8 +242,8 @@ bool matches(const std::list<mask>& regexps, const std::string& str,
|
|||
int ovec[3];
|
||||
if (pcre_exec((*r).regexp, NULL, str.c_str(), str.length(),
|
||||
0, 0, ovec, 3) >= 0) {
|
||||
if (exclude)
|
||||
*exclude = (*r).exclude;
|
||||
if (by_exclusion)
|
||||
*by_exclusion = (*r).exclude;
|
||||
match = ! (*r).exclude;
|
||||
}
|
||||
}
|
||||
|
|
@ -261,12 +273,12 @@ state::~state()
|
|||
|
||||
#endif // DO_CLEANUP
|
||||
|
||||
void state::record_price(const char * setting)
|
||||
void state::record_price(const std::string& setting)
|
||||
{
|
||||
char buf[128];
|
||||
std::strcpy(buf, setting);
|
||||
std::strcpy(buf, setting.c_str());
|
||||
|
||||
assert(std::strlen(setting) < 128);
|
||||
assert(setting.length() < 128);
|
||||
|
||||
char * c = buf;
|
||||
char * p = std::strchr(buf, '=');
|
||||
|
|
@ -278,14 +290,14 @@ void state::record_price(const char * setting)
|
|||
}
|
||||
}
|
||||
|
||||
account * state::find_account(const char * name, bool create)
|
||||
account * state::find_account(const std::string& name, bool create)
|
||||
{
|
||||
accounts_iterator i = accounts_cache.find(name);
|
||||
if (i != accounts_cache.end())
|
||||
return (*i).second;
|
||||
|
||||
char * buf = new char[std::strlen(name) + 1];
|
||||
std::strcpy(buf, name);
|
||||
char * buf = new char[name.length() + 1];
|
||||
std::strcpy(buf, name.c_str());
|
||||
|
||||
account * current = NULL;
|
||||
for (char * tok = std::strtok(buf, ":");
|
||||
|
|
@ -294,8 +306,10 @@ account * state::find_account(const char * name, bool create)
|
|||
if (! current) {
|
||||
accounts_iterator i = accounts.find(tok);
|
||||
if (i == accounts.end()) {
|
||||
if (! create)
|
||||
if (! create) {
|
||||
delete[] buf;
|
||||
return NULL;
|
||||
}
|
||||
current = new account(tok);
|
||||
accounts.insert(accounts_entry(tok, current));
|
||||
} else {
|
||||
|
|
@ -304,8 +318,10 @@ account * state::find_account(const char * name, bool create)
|
|||
} else {
|
||||
accounts_iterator i = current->children.find(tok);
|
||||
if (i == current->children.end()) {
|
||||
if (! create)
|
||||
if (! create) {
|
||||
delete[] buf;
|
||||
return NULL;
|
||||
}
|
||||
current = new account(tok, current);
|
||||
current->parent->children.insert(accounts_entry(tok, current));
|
||||
} else {
|
||||
|
|
|
|||
123
ledger.h
123
ledger.h
|
|
@ -1,5 +1,5 @@
|
|||
#ifndef _LEDGER_H
|
||||
#define _LEDGER_H "$Revision: 1.16 $"
|
||||
#define _LEDGER_H "$Revision: 1.17 $"
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
|
|
@ -26,70 +26,6 @@
|
|||
|
||||
namespace ledger {
|
||||
|
||||
// Format of a ledger entry (GNUcash account files are also supported):
|
||||
//
|
||||
// DATE [CLEARED] (CODE) DESCRIPTION
|
||||
// ACCOUNT AMOUNT [; NOTE]
|
||||
// ACCOUNT AMOUNT [; NOTE]
|
||||
// ...
|
||||
//
|
||||
// The DATE can be YYYY.MM.DD or YYYY/MM/DD or MM/DD.
|
||||
// The CLEARED bit is a '*' if the account has been cleared.
|
||||
// The CODE can be anything, but must be enclosed in parenthesis.
|
||||
// The DESCRIPTION can be anything, up to a newline.
|
||||
//
|
||||
// The ACCOUNT is a colon-separated string naming the account.
|
||||
// The AMOUNT follows the form:
|
||||
// [COMM][WS]QUANTITY[WS][COMM][[WS]@[WS][COMM]PRICE[COMM]]
|
||||
// For example:
|
||||
// 200 AAPL @ $40.00
|
||||
// $50.00
|
||||
// DM 12.54
|
||||
// DM 12.54 @ $1.20
|
||||
// The NOTE can be anything.
|
||||
//
|
||||
// All entries must balance to 0.0, in every commodity. This means
|
||||
// that a transaction with mixed commodities must balance by
|
||||
// converting one of those commodities to the other. As a
|
||||
// convenience, this is done automatically for you in the case where
|
||||
// exactly two commodities are referred to, in which case the second
|
||||
// commodity is converted into the first by computing which the price
|
||||
// must have been in order to balance the transaction. Example:
|
||||
//
|
||||
// 2004.06.18 * (BUY) Apple Computer
|
||||
// Assets:Brokerage $-200.00
|
||||
// Assets:Brokerage 100 AAPL
|
||||
//
|
||||
// What this transaction says is that $200 was paid from the
|
||||
// brokerage account to buy 100 shares of Apple stock, and then place
|
||||
// those same shares back in the brokerage account. From this point
|
||||
// forward, the account "Assets:Brokerage" will have two balance
|
||||
// totals: The number of dollars in the account, and the number of
|
||||
// apple shares.
|
||||
// In terms of the transaction, however, it must balance to zero,
|
||||
// otherwise it would mean that something had been lost without
|
||||
// accouting for it. So in this case what ledger will do is divide
|
||||
// 100 by $200, to arrive at a per-share price of $2 for the APPL
|
||||
// stock, and it will read this transaction as if it had been
|
||||
// written:
|
||||
//
|
||||
// 2004.06.18 * (BUY) Apple Computer
|
||||
// Assets:Brokerage $-200
|
||||
// Assets:Brokerage 100 AAPL @ $2
|
||||
//
|
||||
// If you then wanted to give some of the shares to someone, in
|
||||
// exchange for services rendered, use the regular single-commodity
|
||||
// form of transaction:
|
||||
//
|
||||
// 2004.07.11 * A kick-back for the broker
|
||||
// Assets:Brokerage -10 AAPL
|
||||
// Expenses:Broker's Fees 10 AAPL
|
||||
//
|
||||
// This transaction does not need to know the price of AAPL on the
|
||||
// given day, because none of the shares are being converted to
|
||||
// another commodity. It simply directly affects the total number of
|
||||
// AAPL shares held in "Assets:Brokerage".
|
||||
|
||||
struct commodity
|
||||
{
|
||||
std::string name;
|
||||
|
|
@ -127,9 +63,9 @@ class amount
|
|||
virtual bool has_price() const = 0;
|
||||
virtual void set_value(const amount * pr) = 0;
|
||||
|
||||
// Test if non-zero
|
||||
// Test if the quantity is zero
|
||||
|
||||
virtual operator bool() const = 0;
|
||||
virtual bool is_zero() const = 0;
|
||||
|
||||
// Assignment
|
||||
|
||||
|
|
@ -138,27 +74,28 @@ class amount
|
|||
|
||||
// String conversion routines
|
||||
|
||||
virtual void parse(const char * num) = 0;
|
||||
virtual std::string as_str(bool full_prec = false) const = 0;
|
||||
virtual void parse(const std::string& num) = 0;
|
||||
virtual const std::string as_str(bool full_prec = false) const = 0;
|
||||
};
|
||||
|
||||
extern amount * create_amount(const char * value,
|
||||
extern amount * create_amount(const std::string& value,
|
||||
const amount * cost = NULL);
|
||||
|
||||
struct mask
|
||||
{
|
||||
bool exclude;
|
||||
std::string pattern;
|
||||
pcre * regexp;
|
||||
|
||||
mask(bool exc, pcre * re) : exclude(exc), regexp(re) {}
|
||||
mask(const std::string& pattern);
|
||||
};
|
||||
|
||||
extern std::list<mask> regexps;
|
||||
typedef std::list<mask> regexps_t;
|
||||
|
||||
extern void record_regexp(char * pattern, std::list<mask>& regexps);
|
||||
extern void read_regexps(const char * path, std::list<mask>& regexps);
|
||||
extern bool matches(const std::list<mask>& regexps,
|
||||
const std::string& str, bool * exclude = NULL);
|
||||
void record_regexp(const std::string& pattern, regexps_t& regexps);
|
||||
void read_regexps(const std::string& path, regexps_t& regexps);
|
||||
bool matches(const regexps_t& regexps, const std::string& str,
|
||||
bool * by_exclusion = NULL);
|
||||
|
||||
|
||||
struct account;
|
||||
|
|
@ -187,6 +124,7 @@ struct transaction
|
|||
#endif
|
||||
};
|
||||
|
||||
|
||||
struct entry
|
||||
{
|
||||
std::time_t date;
|
||||
|
|
@ -249,7 +187,7 @@ struct totals
|
|||
}
|
||||
void credit(const totals& other);
|
||||
|
||||
operator bool() const;
|
||||
bool is_zero() const;
|
||||
|
||||
void print(std::ostream& out, int width) const;
|
||||
|
||||
|
|
@ -259,13 +197,6 @@ struct totals
|
|||
}
|
||||
};
|
||||
|
||||
template<class Traits>
|
||||
std::basic_ostream<char, Traits> &
|
||||
operator<<(std::basic_ostream<char, Traits>& out, const totals& t) {
|
||||
t.print(out, 20);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
typedef std::map<const std::string, account *> accounts_t;
|
||||
typedef accounts_t::iterator accounts_iterator;
|
||||
|
|
@ -276,10 +207,14 @@ struct account
|
|||
account * parent;
|
||||
|
||||
std::string name;
|
||||
#ifdef READ_GNUCASH
|
||||
commodity * comm; // default commodity for this account
|
||||
totals balance;
|
||||
#endif
|
||||
totals balance; // optional, parse-time computed balance
|
||||
|
||||
int checked;
|
||||
mutable std::string full_name;
|
||||
|
||||
int checked; // 'balance' uses this for speed's sake
|
||||
#ifdef HUQUQULLAH
|
||||
bool exempt_or_necessary;
|
||||
#endif
|
||||
|
|
@ -301,17 +236,13 @@ struct account
|
|||
const std::string as_str() const {
|
||||
if (! parent)
|
||||
return name;
|
||||
else
|
||||
return parent->as_str() + ":" + name;
|
||||
else if (full_name.empty())
|
||||
full_name = parent->as_str() + ":" + name;
|
||||
|
||||
return full_name;
|
||||
}
|
||||
};
|
||||
|
||||
template<class Traits>
|
||||
std::basic_ostream<char, Traits> &
|
||||
operator<<(std::basic_ostream<char, Traits>& out, const account& a) {
|
||||
return (out << a.as_str());
|
||||
}
|
||||
|
||||
|
||||
struct state
|
||||
{
|
||||
|
|
@ -336,8 +267,8 @@ struct state
|
|||
~state();
|
||||
#endif
|
||||
|
||||
void record_price(const char * setting);
|
||||
account * find_account(const char * name, bool create = true);
|
||||
void record_price(const std::string& setting);
|
||||
account * find_account(const std::string& name, bool create = true);
|
||||
};
|
||||
|
||||
extern state main_ledger;
|
||||
|
|
|
|||
218
ledger.texi
218
ledger.texi
|
|
@ -1,5 +1,5 @@
|
|||
\input texinfo @c -*-texinfo-*-
|
||||
@comment $Id: ledger.texi,v 1.7 2003/10/02 00:07:14 johnw Exp $
|
||||
@comment $Id: ledger.texi,v 1.8 2003/10/02 05:04:38 johnw Exp $
|
||||
@comment %**start of header
|
||||
|
||||
@setfilename ledger.info
|
||||
|
|
@ -24,17 +24,24 @@
|
|||
@contents
|
||||
|
||||
@ifnottex
|
||||
@node Top
|
||||
@node Top, Introduction, (dir), (dir)
|
||||
@top Ledger Accouting Tool
|
||||
|
||||
@c @insertcopying
|
||||
@end ifnottex
|
||||
|
||||
@menu
|
||||
* Introduction::
|
||||
* Keeping a ledger::
|
||||
* Computing Huqúqu'lláh::
|
||||
@end menu
|
||||
|
||||
@node Introduction, Keeping a ledger, Top, Top
|
||||
@chapter Introduction
|
||||
|
||||
@code{ledger} is an accouting tool that has the chutzpah to exist. It
|
||||
provides not one bell or whistle for the money, and returns the user
|
||||
back to the days before user interfaces were even a twinkle on their
|
||||
to the days before user interfaces were even a twinkle in their
|
||||
father's CRT.
|
||||
|
||||
What it does do is provide a double-entry accouting ledger with all of
|
||||
|
|
@ -138,6 +145,7 @@ Your usage of @code{ledger} will have two parts: Keeping the ledger,
|
|||
and using the @code{ledger} tool to provide you with information
|
||||
summaries derived from your ledger's entries.
|
||||
|
||||
@node Keeping a ledger, Computing Huqúqu'lláh, Introduction, Top
|
||||
@chapter Keeping a ledger
|
||||
|
||||
The most important part of accounting is keeping a good ledger. If
|
||||
|
|
@ -192,7 +200,79 @@ the balanced amount, if it is the same as the first line:
|
|||
For this entry, @code{ledger} will figure out that $-23.00 must come
|
||||
from ``Assets:Checking'' in order to balance the entry.
|
||||
|
||||
@section Commodities and currencies
|
||||
@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 @code{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 @code{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?
|
||||
|
||||
Alos, 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
|
||||
|
||||
@code{ledger} makes no assumptions about the commodities you use; it
|
||||
only requires that you specify a commodity. The commodity may be any
|
||||
|
|
@ -292,6 +372,7 @@ Euro=DM 0.75
|
|||
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 @code{ledger}'s accounts and commodity system is so flexible,
|
||||
|
|
@ -329,8 +410,120 @@ would look like:
|
|||
Now you've turned 2 steaks into 15 gold, courtesy of your customer,
|
||||
Sturm Brightblade.
|
||||
|
||||
@chapter Using @code{ledger}
|
||||
@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.
|
||||
|
||||
@chapter Using the @code{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 @code{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 @code{balance} command:
|
||||
|
||||
@example
|
||||
/home/johnw $ export LEDGER=/home/johnw/doc/finance/ledger.dat
|
||||
/home/johnw $ ledger balance
|
||||
@end example
|
||||
|
||||
Here I've set my @code{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
|
||||
@samp{--} to separate the exclusion pattern from the options list:
|
||||
|
||||
@example
|
||||
/home/johnw $ ledger balance -- -equity
|
||||
@end example
|
||||
|
||||
@chapter Using GnuCash to Keep Your Ledger
|
||||
|
||||
The @code{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 @code{ledger}'s data files much easier.
|
||||
|
||||
Until then, you are free to use GnuCash to maintain your ledger, and
|
||||
the @code{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 executable to query it@dots{}
|
||||
|
||||
@node Computing Huqúqu'lláh, , Keeping a ledger, Top
|
||||
@chapter Computing Huqúqu'lláh
|
||||
|
||||
As a Bahá'í, I need to compute Huqúqu'lláh on some of my assets. The
|
||||
|
|
@ -374,20 +567,7 @@ That's it. To see how much Huq
|
|||
ledger data, type:
|
||||
|
||||
@example
|
||||
/home/johnw $ ledger -f ledger.dat balance huquq
|
||||
/home/johnw $ ledger -f ledger.dat balance ^huquq
|
||||
@end example
|
||||
|
||||
Not sure if you should pay yet? Go to your newspaper, or look on the
|
||||
Web, and find the current price of gold per ounce. Then pass this
|
||||
figure to the @samp{-G} option. If it were $357.10, you would use:
|
||||
|
||||
@example
|
||||
/home/johnw $ ledger -f ledger.dat balance -G 357.10 huquq
|
||||
@end example
|
||||
|
||||
Now your balance report will be given in mi@underline{th}qáls of gold,
|
||||
not dollars. If the balance on your Huqúqu'lláh account is more than
|
||||
-19 mi@underline{th}qáls (remember, it is a liability account, so
|
||||
amounts are negative), then get out your checkbook.
|
||||
|
||||
@bye
|
||||
|
|
|
|||
30
main.cc
30
main.cc
|
|
@ -8,10 +8,14 @@ namespace ledger {
|
|||
extern bool parse_gnucash(std::istream& in, bool compute_balances);
|
||||
#endif
|
||||
|
||||
extern void report_balances(int argc, char **argv, std::ostream& out);
|
||||
extern void print_register(int argc, char **argv, std::ostream& out);
|
||||
extern void print_ledger(int argc, char *argv[], std::ostream& out);
|
||||
extern void equity_ledger(int argc, char **argv, std::ostream& out);
|
||||
extern void report_balances(int argc, char ** argv, regexps_t& regexps,
|
||||
std::ostream& out);
|
||||
extern void print_register(int argc, char ** argv, regexps_t& regexps,
|
||||
std::ostream& out);
|
||||
extern void print_ledger(int argc, char ** argv, regexps_t& regexps,
|
||||
std::ostream& out);
|
||||
extern void equity_ledger(int argc, char ** argv, regexps_t& regexps,
|
||||
std::ostream& out);
|
||||
|
||||
bool show_cleared;
|
||||
bool get_quotes;
|
||||
|
|
@ -75,7 +79,7 @@ static const char *formats[] = {
|
|||
NULL
|
||||
};
|
||||
|
||||
static bool parse_date(const char * date_str, std::time_t * result)
|
||||
static bool parse_date(const std::string& date_str, std::time_t * result)
|
||||
{
|
||||
struct std::tm when;
|
||||
|
||||
|
|
@ -84,7 +88,7 @@ static bool parse_date(const char * date_str, std::time_t * result)
|
|||
|
||||
for (const char ** f = formats; *f; f++) {
|
||||
memset(&when, INT_MAX, sizeof(struct std::tm));
|
||||
if (strptime(date_str, *f, &when)) {
|
||||
if (strptime(date_str.c_str(), *f, &when)) {
|
||||
when.tm_hour = 0;
|
||||
when.tm_min = 0;
|
||||
when.tm_sec = 0;
|
||||
|
|
@ -115,10 +119,10 @@ static bool parse_date(const char * date_str, std::time_t * result)
|
|||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
// Parse the command-line options
|
||||
|
||||
std::istream * file = NULL;
|
||||
|
||||
regexps_t regexps;
|
||||
|
||||
#ifdef HUQUQULLAH
|
||||
bool compute_huquq = true;
|
||||
#endif
|
||||
|
|
@ -126,6 +130,8 @@ int main(int argc, char *argv[])
|
|||
have_ending = false;
|
||||
show_cleared = false;
|
||||
|
||||
// Parse the command-line options
|
||||
|
||||
int c;
|
||||
while (-1 != (c = getopt(argc, argv, "+b:e:d:cChHwf:i:p:Pv"))) {
|
||||
switch (char(c)) {
|
||||
|
|
@ -328,13 +334,13 @@ int main(int argc, char *argv[])
|
|||
// Process the command
|
||||
|
||||
if (command == "balance")
|
||||
report_balances(argc - optind, &argv[optind], std::cout);
|
||||
report_balances(argc - optind, &argv[optind], regexps, std::cout);
|
||||
else if (command == "register")
|
||||
print_register(argc - optind, &argv[optind], std::cout);
|
||||
print_register(argc - optind, &argv[optind], regexps, std::cout);
|
||||
else if (command == "print")
|
||||
print_ledger(argc - optind, &argv[optind], std::cout);
|
||||
print_ledger(argc - optind, &argv[optind], regexps, std::cout);
|
||||
else if (command == "equity")
|
||||
equity_ledger(argc - optind, &argv[optind], std::cout);
|
||||
equity_ledger(argc - optind, &argv[optind], regexps, std::cout);
|
||||
}
|
||||
|
||||
// main.cc ends here.
|
||||
|
|
|
|||
8
parse.cc
8
parse.cc
|
|
@ -114,7 +114,9 @@ static void finalize_entry(entry * curr, bool compute_balances)
|
|||
}
|
||||
|
||||
#ifdef HUQUQULLAH
|
||||
if (! main_ledger.compute_huquq || ! (*x)->exempt_or_necessary)
|
||||
if (! main_ledger.compute_huquq ||
|
||||
! ((*x)->exempt_or_necessary ||
|
||||
(*x)->acct->exempt_or_necessary))
|
||||
continue;
|
||||
|
||||
// Reflect 19% of the exempt or necessary transaction in the
|
||||
|
|
@ -307,10 +309,6 @@ bool parse_ledger(std::istream& in, bool compute_balances)
|
|||
#endif
|
||||
|
||||
xact->acct = main_ledger.find_account(p);
|
||||
#ifdef HUQUQULLAH
|
||||
if (xact->acct->exempt_or_necessary)
|
||||
xact->exempt_or_necessary = true;
|
||||
#endif
|
||||
if (compute_balances && xact->cost)
|
||||
xact->acct->balance.credit(xact->cost);
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ static std::string truncated(const std::string& str, int width)
|
|||
// Register printing code
|
||||
//
|
||||
|
||||
void print_register(int argc, char **argv, std::ostream& out)
|
||||
void print_register(int argc, char ** argv, regexps_t& regexps,
|
||||
std::ostream& out)
|
||||
{
|
||||
optind = 1;
|
||||
|
||||
|
|
|
|||
24
report
24
report
|
|
@ -1,20 +1,20 @@
|
|||
#!/bin/bash
|
||||
|
||||
binary=ledger
|
||||
LEDGER=$HOME/doc/finance/ledger.dat
|
||||
|
||||
line="$binary -f $ledger"
|
||||
|
||||
command=$1
|
||||
shift
|
||||
|
||||
case "$command" in
|
||||
balance) $line "$@" balance -s -- -Equity -Income -Expenses -Retirement ;;
|
||||
worth) $line "$@" balance assets liabilities ;;
|
||||
profit) $line "$@" balance income expense ;;
|
||||
spending) $line "$@" balance -F food movies gas tips \
|
||||
balance) ledger "$@" balance -- -Equity -Income -Expenses -Retirement ;;
|
||||
worth) ledger "$@" balance assets liabilities ;;
|
||||
profit) ledger "$@" balance income expense ;;
|
||||
spending) ledger "$@" balance -F food movies gas tips \
|
||||
health supplies -insurance -vacation ;;
|
||||
huquq) $line "$@" balance ^huquq ;;
|
||||
gold) $line "$@" balance -G $1 ^huquq ;;
|
||||
equity) $line "$@" equity -- -^Income -^Expenses -^Equity ;;
|
||||
monthly_spending)
|
||||
for i in jan feb mar apr may jun jul aug sep oct nov dec
|
||||
do
|
||||
echo $i:
|
||||
$0 spending -d $i
|
||||
done ;;
|
||||
huquq) ledger "$@" balance -n ^huquq ;;
|
||||
equity) ledger "$@" equity -- -^Income -^Expenses -^Equity ;;
|
||||
esac
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue