long options are now supported

This commit is contained in:
John Wiegley 2004-08-13 04:30:36 -04:00
parent a4f5abe860
commit eed99acfee
4 changed files with 323 additions and 279 deletions

View file

@ -8,6 +8,7 @@ CODE = account.cc \
error.cc \ error.cc \
format.cc \ format.cc \
ledger.cc \ ledger.cc \
option.cc \
textual.cc \ textual.cc \
valexpr.cc \ valexpr.cc \
walk.cc walk.cc

3
NEWS
View file

@ -36,6 +36,9 @@
- New -Y and -W options prints yearly and weekly subtotals, just as - New -Y and -W options prints yearly and weekly subtotals, just as
the -M option printed monthly subtotals in the previous version. the -M option printed monthly subtotals in the previous version.
- New -w report will show cumulative totals for each of the days of
the week.
- New "-z INTERVAL" allows for more flexible interval reporting. The - New "-z INTERVAL" allows for more flexible interval reporting. The
sublanguage used will probably mature over time, but for now it sublanguage used will probably mature over time, but for now it
supports expression like: supports expression like:

587
main.cc
View file

@ -3,6 +3,7 @@
#include "valexpr.h" #include "valexpr.h"
#include "format.h" #include "format.h"
#include "walk.h" #include "walk.h"
#include "option.h"
#include <fstream> #include <fstream>
#include <cstring> #include <cstring>
@ -78,6 +79,36 @@ void download_price_quote(commodity_t * commodity,
} // namespace ledger } // namespace ledger
namespace {
using namespace ledger;
std::auto_ptr<journal_t> journal(new journal_t);
std::list<std::string> files;
std::auto_ptr<value_expr_t> sort_order;
std::auto_ptr<std::ostream> output_stream;
std::auto_ptr<interval_t> report_interval;
#define OUT() (output_stream.get() ? *output_stream.get() : std::cout)
std::string predicate;
std::string display_predicate;
std::string format_string;
std::string sort_string;
std::string value_expr = "a";
std::string total_expr = "T";
std::time_t interval_begin = 0;
bool show_subtotals = true;
bool show_expanded = false;
bool show_related = false;
bool show_inverted = false;
bool show_empty = false;
bool days_of_the_week = false;
bool show_revalued = false;
bool show_revalued_only = false;
bool use_cache = false;
}
static std::string ledger_cache_file() static std::string ledger_cache_file()
{ {
std::string cache_file; std::string cache_file;
@ -120,13 +151,13 @@ static void show_help(std::ostream& out)
<< " -U show only uncleared transactions and balances\n" << " -U show only uncleared transactions and balances\n"
<< " -R do not consider virtual transactions: real only\n" << " -R do not consider virtual transactions: real only\n"
<< " -l EXPR don't print entries for which EXPR yields 0\n\n" << " -l EXPR don't print entries for which EXPR yields 0\n\n"
<< "Customizing output:\n" << "Output customization:\n"
<< " -n do not calculate parent account totals\n" << " -n do not calculate parent account totals\n"
<< " -s show sub-accounts in balance, and splits in register\n" << " -s show sub-accounts in balance, and splits in register\n"
<< " -M print register using monthly sub-totals\n" << " -M print register using monthly sub-totals\n"
<< " -E show accounts that total to zero\n" << " -E show accounts that total to zero\n"
<< " -S EXPR sort entry output based on EXPR\n\n" << " -S EXPR sort entry output based on EXPR\n\n"
<< "Commodity prices:\n" << "Commodity reporting:\n"
<< " -T report commodity totals, not their market value\n" << " -T report commodity totals, not their market value\n"
<< " -B report cost basis of commodities\n" << " -B report cost basis of commodities\n"
<< " -V report the market value of commodities\n" << " -V report the market value of commodities\n"
@ -143,35 +174,258 @@ static void show_help(std::ostream& out)
<< " equity output equity entries for specified accounts\n"; << " equity output equity entries for specified accounts\n";
} }
int main(int argc, char * argv[])
DEF_OPT_HANDLERS();
//////////////////////////////////////////////////////////////////////
//
// Basic options
OPT_BEGIN(help, "h", false) {
show_help(std::cout);
std::exit(0);
} OPT_END(help);
OPT_BEGIN(version, "v", false) {
show_version(std::cout);
std::exit(0);
} OPT_END(version);
OPT_BEGIN(init_file, "i:", true) {
std::ifstream stream(optarg);
parse_textual_journal(stream, journal.get(), journal->master);
} OPT_END(init_file);
OPT_BEGIN(file, "f:", true) {
files.push_back(optarg);
use_cache = false;
} OPT_END(file);
OPT_BEGIN(output, "o:", false) {
if (std::string(optarg) != "-")
output_stream.reset(new std::ofstream(optarg));
} OPT_END(output);
OPT_BEGIN(set_price, "p:", true) {
if (char * p = std::strchr(optarg, '=')) {
*p = ' ';
std::string conversion = "C ";
conversion += p;
std::istringstream stream(conversion);
parse_textual_journal(stream, journal.get(), journal->master);
} else {
std::cerr << "Error: Invalid price setting: " << optarg
<< std::endl;
std::exit(1);
}
} OPT_END(set_price);
//////////////////////////////////////////////////////////////////////
//
// Report filtering
OPT_BEGIN(begin_date, "b:", false) {
if (! predicate.empty())
predicate += "&";
predicate += "(d>=[";
predicate += optarg;
predicate += "])";
} OPT_END(begin_date);
OPT_BEGIN(end_date, "e:", false) {
if (! predicate.empty())
predicate += "&";
predicate += "(d<[";
predicate += optarg;
predicate += "])";
} OPT_END(end_date);
OPT_BEGIN(current, "c", false) {
if (! predicate.empty())
predicate += "&";
predicate += "(d<t)";
} OPT_END(current);
OPT_BEGIN(cleared, "C", false) {
if (! predicate.empty())
predicate += "&";
predicate += "X";
} OPT_END(cleared);
OPT_BEGIN(uncleared, "U", false) {
if (! predicate.empty())
predicate += "&";
predicate += "!X";
} OPT_END(uncleared);
OPT_BEGIN(real, "R", false) {
if (! predicate.empty())
predicate += "&";
predicate += "R";
} OPT_END(real);
//////////////////////////////////////////////////////////////////////
//
// Output customization
OPT_BEGIN(format, "F:", false) {
format_string = optarg;
} OPT_END(format);
OPT_BEGIN(date_format, "y:", false) {
format_t::date_format = optarg;
} OPT_END(date_format);
OPT_BEGIN(empty, "E", false) {
show_empty = true;
} OPT_END(empty);
OPT_BEGIN(collapse, "n", false) {
show_subtotals = false;
} OPT_END(collapse);
OPT_BEGIN(show_all, "s", false) {
show_expanded = true;
} OPT_END(show_all);
OPT_BEGIN(sort, "S:", false) {
sort_string = optarg;
} OPT_END(sort);
OPT_BEGIN(related, "r", false) {
show_related = true;
} OPT_END(related);
OPT_BEGIN(interval, "z:", false) {
std::string str(optarg);
std::istringstream stream(str);
report_interval.reset(interval_t::parse(stream));
if (! stream.eof()) {
std::string word;
stream >> word;
if (word == "from") {
stream >> word;
if (! parse_date(word.c_str(), &interval_begin))
throw interval_expr_error("Could not parse 'from' date");
}
}
} OPT_END(interval);
OPT_BEGIN(weekly, "W", false) {
report_interval.reset(new interval_t(604800, 0, 0));
} OPT_END(weekly);
OPT_BEGIN(dow, "w", false) {
days_of_the_week = true;
} OPT_END(dow);
OPT_BEGIN(monthly, "M", false) {
report_interval.reset(new interval_t(0, 1, 0));
} OPT_END(monthly);
OPT_BEGIN(yearly, "Y", false) {
report_interval.reset(new interval_t(0, 0, 1));
} OPT_END(yearly);
OPT_BEGIN(limit, "l:", false) {
if (! predicate.empty())
predicate += "&";
predicate += "(";
predicate += optarg;
predicate += ")";
} OPT_END(limit);
OPT_BEGIN(display, "d:", false) {
if (! display_predicate.empty())
display_predicate += "&";
display_predicate += "(";
display_predicate += optarg;
display_predicate += ")";
} OPT_END(display);
OPT_BEGIN(value, "t:", false) {
value_expr = optarg;
} OPT_END(value);
OPT_BEGIN(total, "T:", false) {
total_expr = optarg;
} OPT_END(total);
OPT_BEGIN(value_data, "j", false) {
value_expr = "S" + value_expr;
format_string = plot_value_fmt;
} OPT_END(value_data);
OPT_BEGIN(total_data, "J", false) {
total_expr = "S" + total_expr;
format_string = plot_total_fmt;
} OPT_END(total_data);
//////////////////////////////////////////////////////////////////////
//
// Commodity reporting
OPT_BEGIN(price_db, "P:", false) {
price_db = optarg;
} OPT_END(price_db);
OPT_BEGIN(price_exp, "L:", false) {
pricing_leeway = std::atol(optarg) * 60;
} OPT_END(price_exp);
OPT_BEGIN(download, "Q", false) {
commodity_t::updater = download_price_quote;
} OPT_END(download);
OPT_BEGIN(quantity, "O", false) {
value_expr = "a";
total_expr = "T";
} OPT_END(quantity);
OPT_BEGIN(basis, "B", false) {
value_expr = "c";
total_expr = "C";
} OPT_END(basis);
OPT_BEGIN(market, "V", false) {
show_revalued = true;
value_expr = "v";
total_expr = "V";
} OPT_END(market);
OPT_BEGIN(gain, "G", false) {
show_revalued =
show_revalued_only = true;
value_expr = "c";
total_expr = "G";
} OPT_END(gain);
OPT_BEGIN(average, "A", false) {
value_expr = "a";
total_expr = "MT";
} OPT_END(average);
OPT_BEGIN(deviation, "D", false) {
value_expr = "a";
total_expr = "DMT";
} OPT_END(deviation);
OPT_BEGIN(trend, "X", false) {
value_expr = "a";
total_expr = "MDMT";
} OPT_END(trend);
OPT_BEGIN(weighted_trend, "Z", false) {
value_expr = "a";
total_expr = "MD(MT/(1+(((t-d)/(30*86400))<0?0:((t-d)/(30*86400)))))";
} OPT_END(weighted_trend);
int main(int argc, char * argv[], char * envp[])
{ {
using namespace ledger;
std::auto_ptr<journal_t> journal(new journal_t);
std::list<std::string> files;
std::auto_ptr<value_expr_t> sort_order;
std::auto_ptr<std::ostream> output_stream;
std::auto_ptr<interval_t> report_interval;
#define OUT() (output_stream.get() ? *output_stream.get() : std::cout)
std::string predicate;
std::string display_predicate;
std::string format_string;
std::string sort_string;
std::string value_expr = "a";
std::string total_expr = "T";
std::time_t interval_begin = 0;
bool show_subtotals = true;
bool show_expanded = false;
bool show_related = false;
bool show_inverted = false;
bool show_empty = false;
bool days_of_the_week = false;
bool show_revalued = false;
bool show_revalued_only = false;
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED
if (char * p = std::getenv("DEBUG_FILE")) { if (char * p = std::getenv("DEBUG_FILE")) {
debug_stream = new std::ofstream(p); debug_stream = new std::ofstream(p);
@ -181,6 +435,7 @@ int main(int argc, char * argv[])
// Initialize some variables based on environment variable settings // Initialize some variables based on environment variable settings
// jww (2004-08-13): fix these
if (char * p = std::getenv("PRICE_HIST")) if (char * p = std::getenv("PRICE_HIST"))
price_db = p; price_db = p;
@ -189,11 +444,13 @@ int main(int argc, char * argv[])
// A ledger data file must be specified // A ledger data file must be specified
bool use_cache = std::getenv("LEDGER") != NULL; use_cache = std::getenv("LEDGER") != NULL;
if (use_cache) { if (use_cache) {
// jww (2004-08-13): fix this
for (int i = 0; i < argc; i++) for (int i = 0; i < argc; i++)
if (std::strcmp(argv[i], "-f") == 0) { if (std::strcmp(argv[i], "-f") == 0 ||
std::strcmp(argv[i], "--file") == 0) {
use_cache = false; use_cache = false;
break; break;
} }
@ -218,251 +475,19 @@ int main(int argc, char * argv[])
// Parse the command-line options // Parse the command-line options
int c, index; std::vector<char *> args;
while (-1 != process_arguments(args, argc, argv);
(c = getopt(argc, argv,
"+ABb:Ccd:DEe:F:f:Ghi:JjL:l:MnOo:P:p:QRrS:sT:t:UVvWwXYy:Zz:"))) {
switch (char(c)) {
// Basic options
case 'h':
show_help(std::cout);
break;
case 'v': if (args.empty()) {
show_version(std::cout);
return 0;
case 'f':
files.push_back(optarg);
use_cache = false;
break;
case 'o':
if (std::string(optarg) != "-")
output_stream.reset(new std::ofstream(optarg));
break;
case 'p':
if (char * p = std::strchr(optarg, '=')) {
*p = ' ';
std::string conversion = "C ";
conversion += p;
std::istringstream stream(conversion);
parse_textual_journal(stream, journal.get(), journal->master);
}
break;
case 'b':
if (! predicate.empty())
predicate += "&";
predicate += "(d>=[";
predicate += optarg;
predicate += "])";
break;
case 'e':
if (! predicate.empty())
predicate += "&";
predicate += "(d<[";
predicate += optarg;
predicate += "])";
break;
case 'c': {
if (! predicate.empty())
predicate += "&";
predicate += "(d<";
std::ostringstream now;
now << std::time(NULL);
predicate += now.str();
predicate += ")";
break;
}
case 'C':
if (! predicate.empty())
predicate += "&";
predicate += "X";
break;
case 'U':
if (! predicate.empty())
predicate += "&";
predicate += "!X";
break;
case 'R':
if (! predicate.empty())
predicate += "&";
predicate += "R";
break;
// Customizing output
case 'F':
format_string = optarg;
break;
case 'y':
format_t::date_format = optarg;
break;
case 'E':
show_empty = true;
break;
case 'n':
show_subtotals = false;
break;
case 's':
show_expanded = true;
break;
case 'S':
sort_string = optarg;
break;
case 'r':
show_related = true;
break;
case 'z': {
std::string str(optarg);
std::istringstream stream(str);
report_interval.reset(interval_t::parse(stream));
if (! stream.eof()) {
std::string word;
stream >> word;
if (word == "from") {
stream >> word;
if (! parse_date(word.c_str(), &interval_begin))
throw interval_expr_error("Could not parse 'from' date");
}
}
break;
}
case 'W':
report_interval.reset(new interval_t(604800, 0, 0));
break;
case 'w':
days_of_the_week = true;
break;
case 'M':
report_interval.reset(new interval_t(0, 1, 0));
break;
case 'Y':
report_interval.reset(new interval_t(0, 0, 1));
break;
case 'l':
if (! predicate.empty())
predicate += "&";
predicate += "(";
predicate += optarg;
predicate += ")";
break;
case 'd':
if (! display_predicate.empty())
display_predicate += "&";
display_predicate += "(";
display_predicate += optarg;
display_predicate += ")";
break;
// Commodity reporting
case 'P':
price_db = optarg;
break;
case 'L':
pricing_leeway = std::atol(optarg) * 60;
break;
case 'Q':
commodity_t::updater = download_price_quote;
break;
case 't':
value_expr = optarg;
break;
case 'T':
total_expr = optarg;
break;
case 'O':
value_expr = "a";
total_expr = "T";
break;
case 'B':
value_expr = "c";
total_expr = "C";
break;
case 'V':
show_revalued = true;
value_expr = "v";
total_expr = "V";
break;
case 'G':
show_revalued =
show_revalued_only = true;
value_expr = "c";
total_expr = "G";
break;
case 'A':
value_expr = "a";
total_expr = "MT";
break;
case 'D':
value_expr = "a";
total_expr = "DMT";
break;
case 'X':
value_expr = "a";
total_expr = "MDMT";
break;
case 'Z':
value_expr = "a";
total_expr = "MD(MT/(1+(((t-d)/(30*86400))<0?0:((t-d)/(30*86400)))))";
break;
case 'j':
value_expr = "S" + value_expr;
format_string = plot_value_fmt;
break;
case 'J':
total_expr = "S" + total_expr;
format_string = plot_total_fmt;
break;
default:
assert(0);
break;
}
}
if (optind == argc) {
show_help(std::cerr); show_help(std::cerr);
return 1; return 1;
} }
argc = args.size();
int index = 0;
index = optind; // Process options from the environment
process_environment(envp);
// Read the ledger file, unless we already read it from the cache // Read the ledger file, unless we already read it from the cache
@ -509,7 +534,7 @@ int main(int argc, char * argv[])
// Read the command word, and then check and simplify it // Read the command word, and then check and simplify it
std::string command = argv[index++]; std::string command = args[index++];
if (command == "balance" || command == "bal" || command == "b") if (command == "balance" || command == "bal" || command == "b")
command = "b"; command = "b";
@ -531,20 +556,20 @@ int main(int argc, char * argv[])
std::auto_ptr<entry_t> new_entry; std::auto_ptr<entry_t> new_entry;
if (command == "e") { if (command == "e") {
new_entry.reset(journal->derive_entry(argc - index, &argv[index])); new_entry.reset(journal->derive_entry(argc - index, &args[index]));
} else { } else {
// Treat the remaining command-line arguments as regular // Treat the remaining command-line arguments as regular
// expressions, used for refining report results. // expressions, used for refining report results.
int start = index; int start = index;
for (; index < argc; index++) for (; index < argc; index++)
if (std::strcmp(argv[index], "--") == 0) { if (std::strcmp(args[index], "--") == 0) {
index++; index++;
break; break;
} }
if (start < index) { if (start < index) {
std::list<std::string> regexps(&argv[start], &argv[index]); std::list<std::string> regexps(&args[start], &args[index]);
std::string pred = regexps_to_predicate(regexps.begin(), regexps.end()); std::string pred = regexps_to_predicate(regexps.begin(), regexps.end());
if (! pred.empty()) { if (! pred.empty()) {
if (! predicate.empty()) if (! predicate.empty())
@ -554,7 +579,7 @@ int main(int argc, char * argv[])
} }
if (index < argc) { if (index < argc) {
std::list<std::string> regexps(&argv[index], &argv[argc]); std::list<std::string> regexps(&args[index], &args[argc]);
std::string pred = regexps_to_predicate(regexps.begin(), regexps.end(), std::string pred = regexps_to_predicate(regexps.begin(), regexps.end(),
false); false);
if (! pred.empty()) { if (! pred.empty()) {
@ -583,7 +608,7 @@ int main(int argc, char * argv[])
} }
} }
// Compile the sorting string // Compile the sort criteria
if (! sort_string.empty()) { if (! sort_string.empty()) {
try { try {
@ -625,7 +650,7 @@ int main(int argc, char * argv[])
return 1; return 1;
} }
// Now handle the command that was identified above. // Configure some option depending on the report type
bool show_all_related = false; bool show_all_related = false;
@ -643,6 +668,8 @@ int main(int argc, char * argv[])
show_all_related = true; show_all_related = true;
} }
// Compile the format strings
const char * f; const char * f;
if (! format_string.empty()) if (! format_string.empty())
f = format_string.c_str(); f = format_string.c_str();
@ -668,6 +695,8 @@ int main(int argc, char * argv[])
format_t format(first_line_format); format_t format(first_line_format);
format_t nformat(next_lines_format); format_t nformat(next_lines_format);
// Walk the entries based on the report type and the options
if (command == "b") { if (command == "b") {
std::auto_ptr<item_handler<transaction_t> > formatter; std::auto_ptr<item_handler<transaction_t> > formatter;
formatter.reset(new add_to_account_value); formatter.reset(new add_to_account_value);

View file

@ -2,6 +2,7 @@
#include "autoxact.h" #include "autoxact.h"
#include "valexpr.h" #include "valexpr.h"
#include "error.h" #include "error.h"
#include "option.h"
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
@ -470,6 +471,16 @@ unsigned int parse_textual_journal(std::istream& in, journal_t * journal,
linenum++; linenum++;
break; break;
case ':': { // option setting
std::string opt;
in >> c;
in >> opt;
in.getline(line, MAX_LINE);
linenum++;
process_option(opt, line + 1);
break;
}
case '=': // automated transactions case '=': // automated transactions
parse_automated_transactions(in, account_stack.front(), auto_xacts); parse_automated_transactions(in, account_stack.front(), auto_xacts);
break; break;