Redid the way command-line arguments are processed. Before, Ledger used - and

-- to mean special things after the command verb was seen.  But now, what used
to be specified as this:

  ledger -n reg cash -payable -- shell

Is now specified as this:

  ledger reg -n cash not payable @shell

It could also be specified as:

  ledger -n reg \(cash and not payable\) and @shell
This commit is contained in:
John Wiegley 2009-01-22 16:27:24 -04:00
parent ccedf7d57f
commit 0b9f22b4d2
6 changed files with 141 additions and 120 deletions

View file

@ -102,7 +102,7 @@ namespace ledger {
TRACE_START(arguments, 1, "Processing command-line arguments");
strings_list args;
process_arguments(argc - 1, argv + 1, false, report, args);
process_arguments(argc - 1, argv + 1, report, args);
if (args.empty()) {
ledger::help(std::cout);

View file

@ -139,31 +139,36 @@ void process_environment(const char ** envp, const string& tag,
}
}
void process_arguments(int, char ** argv, const bool anywhere,
scope_t& scope, std::list<string>& args)
void process_arguments(int, char ** argv, scope_t& scope,
std::list<string>& args)
{
bool anywhere = true;
for (char ** i = argv; *i; i++) {
if ((*i)[0] != '-') {
if (anywhere) {
args.push_back(*i);
continue;
} else {
for (; *i; i++)
args.push_back(*i);
break;
}
DEBUG("option.args", "Examining argument '" << *i << "'");
if (! anywhere || (*i)[0] != '-') {
DEBUG("option.args", " adding to list of real args");
args.push_back(*i);
continue;
}
// --long-option or -s
if ((*i)[1] == '-') {
if ((*i)[2] == '\0')
break;
if ((*i)[2] == '\0') {
DEBUG("option.args", " it's a --, ending options processing");
anywhere = false;
continue;
}
DEBUG("option.args", " it's an option string");
char * name = *i + 2;
char * value = NULL;
if (char * p = std::strchr(name, '=')) {
*p++ = '\0';
value = p;
DEBUG("option.args", " read option value from option: " << value);
}
op_bool_tuple opt(find_option(scope, name));
@ -172,6 +177,7 @@ void process_arguments(int, char ** argv, const bool anywhere,
if (opt.get<1>() && value == NULL) {
value = *++i;
DEBUG("option.args", " read option value from arg: " << value);
if (value == NULL)
throw_(option_error, "missing option argument for --" << name);
}
@ -181,6 +187,8 @@ void process_arguments(int, char ** argv, const bool anywhere,
throw_(option_error, "illegal option -");
}
else {
DEBUG("option.args", " single-char option");
typedef tuple<expr_t::ptr_op_t, bool, char> op_bool_char_tuple;
std::list<op_bool_char_tuple> option_queue;
@ -199,6 +207,7 @@ void process_arguments(int, char ** argv, const bool anywhere,
char * value = NULL;
if (o.get<1>()) {
value = *++i;
DEBUG("option.args", " read option value from arg: " << value);
if (value == NULL)
throw_(option_error,
"missing option argument for -" << o.get<2>());

View file

@ -42,8 +42,8 @@ void process_option(const string& name, scope_t& scope,
void process_environment(const char ** envp, const string& tag,
scope_t& scope);
void process_arguments(int argc, char ** argv, const bool anywhere,
scope_t& scope, std::list<string>& args);
void process_arguments(int argc, char ** argv, scope_t& scope,
std::list<string>& args);
DECLARE_EXCEPTION(option_error, std::runtime_error);

View file

@ -215,8 +215,9 @@ expr_t::parser_t::parse_logic_expr(std::istream& in,
if (node && ! (tflags & EXPR_PARSE_SINGLE)) {
op_t::kind_t kind = op_t::LAST;
flags_t _flags = tflags;
flags_t _flags = tflags;
token_t& tok = next_token(in, tflags);
bool negate = false;
switch (tok.kind) {
case token_t::EQUAL:
@ -226,11 +227,16 @@ expr_t::parser_t::parse_logic_expr(std::istream& in,
kind = op_t::O_EQ;
break;
case token_t::NEQUAL:
kind = op_t::O_NEQ;
kind = op_t::O_EQ;
negate = true;
break;
case token_t::MATCH:
kind = op_t::O_MATCH;
break;
case token_t::NMATCH:
kind = op_t::O_MATCH;
negate = true;
break;
case token_t::LESS:
kind = op_t::O_LT;
break;
@ -257,6 +263,12 @@ expr_t::parser_t::parse_logic_expr(std::istream& in,
if (! node->right())
throw_(parse_error,
tok.symbol << " operator not followed by argument");
if (negate) {
prev = node;
node = new op_t(op_t::O_NOT);
node->set_left(prev);
}
}
}

View file

@ -321,99 +321,114 @@ namespace {
string args_to_predicate(value_t::sequence_t::const_iterator begin,
value_t::sequence_t::const_iterator end)
{
string acct_value_expr;
string payee_value_expr;
string note_value_expr;
std::ostringstream expr;
bool append_and = false;
string * value_expr;
enum regexp_kind_t {
ACCOUNT_REGEXP,
PAYEE_REGEXP,
NOTE_REGEXP
}
kind = ACCOUNT_REGEXP;
value_expr = &acct_value_expr;
for ( ; begin != end; begin++) {
while (begin != end) {
const string& arg((*begin).as_string());
if (arg == "--") {
kind = PAYEE_REGEXP;
value_expr = &payee_value_expr;
bool parse_argument = true;
if (arg == "not") {
expr << " ! ";
parse_argument = false;
append_and = false;
}
else if (arg == "/") {
kind = NOTE_REGEXP;
value_expr = &note_value_expr;
else if (arg == "and") {
expr << " & ";
parse_argument = false;
append_and = false;
}
else if (arg == "or") {
expr << " | ";
parse_argument = false;
append_and = false;
}
else if (append_and) {
expr << " & ";
}
else {
if (! value_expr->empty())
*value_expr += "|";
switch (kind) {
case ACCOUNT_REGEXP:
*value_expr += "account =~ ";
break;
case PAYEE_REGEXP:
*value_expr += "payee =~ ";
break;
case NOTE_REGEXP:
*value_expr += "note =~ ";
break;
}
const char * p = arg.c_str();
if (*p == '-') {
*value_expr += "!";
p++;
}
*value_expr += "/";
if (kind == NOTE_REGEXP) *value_expr += ":";
while (*p) {
if (*p == '/')
*value_expr += "\\";
*value_expr += *p;
p++;
}
if (kind == NOTE_REGEXP) *value_expr += ":";
*value_expr += "/";
append_and = true;
}
if (parse_argument) {
const char * p = arg.c_str();
bool in_prefix = true;
bool in_suffix = false;
bool found_specifier = false;
bool saw_tag_char = false;
for (const char * c = p; *c != '\0'; c++) {
bool consumed = false;
if (in_prefix) {
switch (*c) {
case '(':
break;
case '@':
expr << "(payee =~ /";
found_specifier = true;
consumed = true;
break;
case '=':
expr << "(note =~ /";
found_specifier = true;
consumed = true;
break;
case '%':
expr << "(note =~ /:";
found_specifier = true;
saw_tag_char = true;
consumed = true;
break;
case '/':
case '_':
default:
if (! found_specifier) {
expr << "(account =~ /";
found_specifier = true;
}
in_prefix = false;
break;
}
} else {
switch (*c) {
case ')':
if (! in_suffix) {
if (found_specifier) {
if (saw_tag_char)
expr << ':';
expr << "/)";
}
in_suffix = true;
}
break;
default:
if (in_suffix)
throw_(parse_error, "Invalid text in specification argument");
break;
}
}
if (! consumed)
expr << *c;
}
if (! in_suffix) {
if (found_specifier) {
if (saw_tag_char)
expr << ':';
expr << "/)";
}
}
}
begin++;
}
string final_value_expr;
DEBUG("report.predicate", "Regexp predicate expression = " << expr.str());
if (! acct_value_expr.empty()) {
if (! payee_value_expr.empty() ||
! note_value_expr.empty())
final_value_expr = string("(") + acct_value_expr + ")";
else
final_value_expr = acct_value_expr;
}
if (! payee_value_expr.empty()) {
if (! acct_value_expr.empty())
final_value_expr += string("&(") + payee_value_expr + ")";
else if (! note_value_expr.empty())
final_value_expr = string("(") + payee_value_expr + ")";
else
final_value_expr = payee_value_expr;
}
if (! note_value_expr.empty()) {
if (! acct_value_expr.empty() ||
! payee_value_expr.empty())
final_value_expr += string("&(") + note_value_expr + ")";
else if (acct_value_expr.empty() &&
payee_value_expr.empty())
final_value_expr = note_value_expr;
}
DEBUG("report.predicate",
"Regexp predicate expression = " << final_value_expr);
return final_value_expr;
return expr.str();
}
template <class Type = xact_t,

View file

@ -224,27 +224,12 @@ See LICENSE file included with the distribution for details and disclaimer.\n";
// Option handlers
//
value_t option_file_(call_scope_t& args) {
value_t option_file_(call_scope_t& args) { // f
assert(args.size() == 1);
// jww (2008-08-13): Add support for multiple files, but not between
// -f and LEDGER_FILE
if (data_file.empty()) {
data_file = args[0].as_string();
use_cache = false;
#if 0
// jww (2008-08-14): Should we check whether the file exists
// before we accept it, or is this done later on?
if (! data_file.string() == "-") {
std::string path = resolve_path(optarg);
if (access(path.c_str(), R_OK) != -1)
config->data_file = path;
else
throw_(std::invalid_argument,
"The ledger file '" << path << "' does not exist or is not readable");
}
#endif
}
// jww (2008-08-13): Add support for multiple files
data_file = args[0].as_string();
use_cache = false;
return true;
}