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:
parent
ccedf7d57f
commit
0b9f22b4d2
6 changed files with 141 additions and 120 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>());
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
181
src/report.cc
181
src/report.cc
|
|
@ -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 = ¬e_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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue