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"); TRACE_START(arguments, 1, "Processing command-line arguments");
strings_list args; strings_list args;
process_arguments(argc - 1, argv + 1, false, report, args); process_arguments(argc - 1, argv + 1, report, args);
if (args.empty()) { if (args.empty()) {
ledger::help(std::cout); 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, void process_arguments(int, char ** argv, scope_t& scope,
scope_t& scope, std::list<string>& args) std::list<string>& args)
{ {
bool anywhere = true;
for (char ** i = argv; *i; i++) { for (char ** i = argv; *i; i++) {
if ((*i)[0] != '-') { DEBUG("option.args", "Examining argument '" << *i << "'");
if (anywhere) {
if (! anywhere || (*i)[0] != '-') {
DEBUG("option.args", " adding to list of real args");
args.push_back(*i); args.push_back(*i);
continue; continue;
} else {
for (; *i; i++)
args.push_back(*i);
break;
}
} }
// --long-option or -s // --long-option or -s
if ((*i)[1] == '-') { if ((*i)[1] == '-') {
if ((*i)[2] == '\0') if ((*i)[2] == '\0') {
break; 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 * name = *i + 2;
char * value = NULL; char * value = NULL;
if (char * p = std::strchr(name, '=')) { if (char * p = std::strchr(name, '=')) {
*p++ = '\0'; *p++ = '\0';
value = p; value = p;
DEBUG("option.args", " read option value from option: " << value);
} }
op_bool_tuple opt(find_option(scope, name)); 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) { if (opt.get<1>() && value == NULL) {
value = *++i; value = *++i;
DEBUG("option.args", " read option value from arg: " << value);
if (value == NULL) if (value == NULL)
throw_(option_error, "missing option argument for --" << name); 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 -"); throw_(option_error, "illegal option -");
} }
else { else {
DEBUG("option.args", " single-char option");
typedef tuple<expr_t::ptr_op_t, bool, char> op_bool_char_tuple; typedef tuple<expr_t::ptr_op_t, bool, char> op_bool_char_tuple;
std::list<op_bool_char_tuple> option_queue; std::list<op_bool_char_tuple> option_queue;
@ -199,6 +207,7 @@ void process_arguments(int, char ** argv, const bool anywhere,
char * value = NULL; char * value = NULL;
if (o.get<1>()) { if (o.get<1>()) {
value = *++i; value = *++i;
DEBUG("option.args", " read option value from arg: " << value);
if (value == NULL) if (value == NULL)
throw_(option_error, throw_(option_error,
"missing option argument for -" << o.get<2>()); "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, void process_environment(const char ** envp, const string& tag,
scope_t& scope); scope_t& scope);
void process_arguments(int argc, char ** argv, const bool anywhere, void process_arguments(int argc, char ** argv, scope_t& scope,
scope_t& scope, std::list<string>& args); std::list<string>& args);
DECLARE_EXCEPTION(option_error, std::runtime_error); DECLARE_EXCEPTION(option_error, std::runtime_error);

View file

@ -217,6 +217,7 @@ expr_t::parser_t::parse_logic_expr(std::istream& in,
op_t::kind_t kind = op_t::LAST; op_t::kind_t kind = op_t::LAST;
flags_t _flags = tflags; flags_t _flags = tflags;
token_t& tok = next_token(in, tflags); token_t& tok = next_token(in, tflags);
bool negate = false;
switch (tok.kind) { switch (tok.kind) {
case token_t::EQUAL: case token_t::EQUAL:
@ -226,11 +227,16 @@ expr_t::parser_t::parse_logic_expr(std::istream& in,
kind = op_t::O_EQ; kind = op_t::O_EQ;
break; break;
case token_t::NEQUAL: case token_t::NEQUAL:
kind = op_t::O_NEQ; kind = op_t::O_EQ;
negate = true;
break; break;
case token_t::MATCH: case token_t::MATCH:
kind = op_t::O_MATCH; kind = op_t::O_MATCH;
break; break;
case token_t::NMATCH:
kind = op_t::O_MATCH;
negate = true;
break;
case token_t::LESS: case token_t::LESS:
kind = op_t::O_LT; kind = op_t::O_LT;
break; break;
@ -257,6 +263,12 @@ expr_t::parser_t::parse_logic_expr(std::istream& in,
if (! node->right()) if (! node->right())
throw_(parse_error, throw_(parse_error,
tok.symbol << " operator not followed by argument"); 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, string args_to_predicate(value_t::sequence_t::const_iterator begin,
value_t::sequence_t::const_iterator end) value_t::sequence_t::const_iterator end)
{ {
string acct_value_expr; std::ostringstream expr;
string payee_value_expr; bool append_and = false;
string note_value_expr;
string * value_expr; while (begin != end) {
enum regexp_kind_t {
ACCOUNT_REGEXP,
PAYEE_REGEXP,
NOTE_REGEXP
}
kind = ACCOUNT_REGEXP;
value_expr = &acct_value_expr;
for ( ; begin != end; begin++) {
const string& arg((*begin).as_string()); const string& arg((*begin).as_string());
if (arg == "--") { bool parse_argument = true;
kind = PAYEE_REGEXP;
value_expr = &payee_value_expr; if (arg == "not") {
expr << " ! ";
parse_argument = false;
append_and = false;
} }
else if (arg == "/") { else if (arg == "and") {
kind = NOTE_REGEXP; expr << " & ";
value_expr = &note_value_expr; parse_argument = false;
append_and = false;
}
else if (arg == "or") {
expr << " | ";
parse_argument = false;
append_and = false;
}
else if (append_and) {
expr << " & ";
} }
else { else {
if (! value_expr->empty()) append_and = true;
*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;
} }
if (parse_argument) {
const char * p = arg.c_str(); const char * p = arg.c_str();
if (*p == '-') {
*value_expr += "!";
p++;
}
*value_expr += "/"; bool in_prefix = true;
if (kind == NOTE_REGEXP) *value_expr += ":"; bool in_suffix = false;
while (*p) { bool found_specifier = false;
if (*p == '/') bool saw_tag_char = false;
*value_expr += "\\";
*value_expr += *p; for (const char * c = p; *c != '\0'; c++) {
p++; 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;
} }
if (kind == NOTE_REGEXP) *value_expr += ":"; in_prefix = false;
*value_expr += "/"; 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;
} }
} }
string final_value_expr; if (! consumed)
expr << *c;
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 (! in_suffix) {
if (! acct_value_expr.empty()) if (found_specifier) {
final_value_expr += string("&(") + payee_value_expr + ")"; if (saw_tag_char)
else if (! note_value_expr.empty()) expr << ':';
final_value_expr = string("(") + payee_value_expr + ")"; expr << "/)";
else }
final_value_expr = payee_value_expr; }
} }
if (! note_value_expr.empty()) { begin++;
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", DEBUG("report.predicate", "Regexp predicate expression = " << expr.str());
"Regexp predicate expression = " << final_value_expr);
return final_value_expr; return expr.str();
} }
template <class Type = xact_t, template <class Type = xact_t,

View file

@ -224,28 +224,13 @@ See LICENSE file included with the distribution for details and disclaimer.\n";
// Option handlers // Option handlers
// //
value_t option_file_(call_scope_t& args) { value_t option_file_(call_scope_t& args) { // f
assert(args.size() == 1); assert(args.size() == 1);
// jww (2008-08-13): Add support for multiple files, but not between
// -f and LEDGER_FILE // jww (2008-08-13): Add support for multiple files
if (data_file.empty()) {
data_file = args[0].as_string(); data_file = args[0].as_string();
use_cache = false; 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
}
return true; return true;
} }
}; };