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");
|
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);
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
|
||||||
args.push_back(*i);
|
if (! anywhere || (*i)[0] != '-') {
|
||||||
continue;
|
DEBUG("option.args", " adding to list of real args");
|
||||||
} else {
|
args.push_back(*i);
|
||||||
for (; *i; i++)
|
continue;
|
||||||
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>());
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -215,8 +215,9 @@ expr_t::parser_t::parse_logic_expr(std::istream& in,
|
||||||
|
|
||||||
if (node && ! (tflags & EXPR_PARSE_SINGLE)) {
|
if (node && ! (tflags & EXPR_PARSE_SINGLE)) {
|
||||||
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
181
src/report.cc
181
src/report.cc
|
|
@ -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 = ¬e_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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 += "/";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()) {
|
return expr.str();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Type = xact_t,
|
template <class Type = xact_t,
|
||||||
|
|
|
||||||
|
|
@ -224,27 +224,12 @@ 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
|
|
||||||
if (data_file.empty()) {
|
|
||||||
data_file = args[0].as_string();
|
|
||||||
use_cache = false;
|
|
||||||
|
|
||||||
#if 0
|
// jww (2008-08-13): Add support for multiple files
|
||||||
// jww (2008-08-14): Should we check whether the file exists
|
data_file = args[0].as_string();
|
||||||
// before we accept it, or is this done later on?
|
use_cache = false;
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue