From 3f899c93e655945a775eecfe81d49fff8befba11 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Tue, 22 Jun 2010 03:20:24 -0400 Subject: Added new "bold" modifier to query expressions For example: ledger bal assets bold checking Or you can use expressions: ledger bal assets bold '=total > 1000' This last is identical to saying: ledger bal -l 'account =~ /assets/' --bold-if='total > 1000' --- src/query.h | 94 ++++++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 59 insertions(+), 35 deletions(-) (limited to 'src/query.h') diff --git a/src/query.h b/src/query.h index 02f8f4e7..e953206a 100644 --- a/src/query.h +++ b/src/query.h @@ -46,7 +46,7 @@ namespace ledger { -class query_t : public predicate_t +class query_t { protected: class parser_t; @@ -81,7 +81,6 @@ public: TOK_OR, TOK_EQ, - TOK_DATE, TOK_CODE, TOK_PAYEE, TOK_NOTE, @@ -89,6 +88,9 @@ public: TOK_META, TOK_EXPR, + TOK_SHOW, + TOK_BOLD, + TERM, END_REACHED @@ -131,13 +133,14 @@ public: case TOK_AND: return "TOK_AND"; case TOK_OR: return "TOK_OR"; case TOK_EQ: return "TOK_EQ"; - case TOK_DATE: return "TOK_DATE"; case TOK_CODE: return "TOK_CODE"; case TOK_PAYEE: return "TOK_PAYEE"; case TOK_NOTE: return "TOK_NOTE"; case TOK_ACCOUNT: return "TOK_ACCOUNT"; case TOK_META: return "TOK_META"; case TOK_EXPR: return "TOK_EXPR"; + case TOK_SHOW: return "TOK_SHOW"; + case TOK_BOLD: return "TOK_BOLD"; case TERM: return string("TERM(") + *value + ")"; case END_REACHED: return "END_REACHED"; } @@ -153,13 +156,14 @@ public: case TOK_AND: return "and"; case TOK_OR: return "or"; case TOK_EQ: return "="; - case TOK_DATE: return "date"; case TOK_CODE: return "code"; case TOK_PAYEE: return "payee"; case TOK_NOTE: return "note"; case TOK_ACCOUNT: return "account"; case TOK_META: return "meta"; case TOK_EXPR: return "expr"; + case TOK_SHOW: return "show"; + case TOK_BOLD: return "bold"; case END_REACHED: return ""; @@ -218,24 +222,38 @@ public: } }; + enum kind_t { + QUERY_LIMIT, + QUERY_SHOW, + QUERY_BOLD + }; + + typedef std::map query_map_t; + protected: class parser_t { friend class query_t; - value_t args; - lexer_t lexer; + value_t args; + lexer_t lexer; + keep_details_t what_to_keep; + query_map_t query_map; expr_t::ptr_op_t parse_query_term(lexer_t::token_t::kind_t tok_context); expr_t::ptr_op_t parse_unary_expr(lexer_t::token_t::kind_t tok_context); expr_t::ptr_op_t parse_and_expr(lexer_t::token_t::kind_t tok_context); expr_t::ptr_op_t parse_or_expr(lexer_t::token_t::kind_t tok_context); - expr_t::ptr_op_t parse_query_expr(lexer_t::token_t::kind_t tok_context); + expr_t::ptr_op_t parse_query_expr(lexer_t::token_t::kind_t tok_context, + bool subexpression = false); public: - parser_t(const value_t& _args, bool multiple_args = true) - : args(_args), lexer(args.begin(), args.end(), multiple_args) { - TRACE_CTOR(query_t::parser_t, ""); + parser_t(const value_t& _args, + const keep_details_t& _what_to_keep = keep_details_t(), + bool multiple_args = true) + : args(_args), lexer(args.begin(), args.end(), multiple_args), + what_to_keep(_what_to_keep) { + TRACE_CTOR(query_t::parser_t, "value_t, keep_details_t, bool"); } parser_t(const parser_t& parser) : args(parser.args), lexer(parser.lexer) { @@ -245,8 +263,8 @@ protected: TRACE_DTOR(query_t::parser_t); } - expr_t::ptr_op_t parse() { - return parse_query_expr(lexer_t::token_t::TOK_ACCOUNT); + expr_t::ptr_op_t parse(bool subexpression = false) { + return parse_query_expr(lexer_t::token_t::TOK_ACCOUNT, subexpression); } bool tokens_remaining() { @@ -257,55 +275,61 @@ protected: }; optional parser; + query_map_t predicates; public: query_t() { TRACE_CTOR(query_t, ""); } query_t(const query_t& other) - : predicate_t(other) { + : parser(other.parser), predicates(other.predicates) { TRACE_CTOR(query_t, "copy"); } - query_t(const string& arg, - const keep_details_t& _what_to_keep = keep_details_t(), - bool multiple_args = true) - : predicate_t(_what_to_keep) { - TRACE_CTOR(query_t, "string, keep_details_t"); + query_t(const string& arg, + const keep_details_t& what_to_keep = keep_details_t(), + bool multiple_args = true) { + TRACE_CTOR(query_t, "string, keep_details_t, bool"); if (! arg.empty()) { value_t temp(string_value(arg)); - parse_args(temp.to_sequence(), multiple_args); + parse_args(temp.to_sequence(), what_to_keep, multiple_args); } } - query_t(const value_t& args, - const keep_details_t& _what_to_keep = keep_details_t(), - bool multiple_args = true) - : predicate_t(_what_to_keep) { - TRACE_CTOR(query_t, "value_t, keep_details_t"); + query_t(const value_t& args, + const keep_details_t& what_to_keep = keep_details_t(), + bool multiple_args = true) { + TRACE_CTOR(query_t, "value_t, keep_details_t, bool"); if (! args.empty()) - parse_args(args, multiple_args); + parse_args(args, what_to_keep, multiple_args); } virtual ~query_t() { TRACE_DTOR(query_t); } - void parse_args(const value_t& args, bool multiple_args = true) { + expr_t::ptr_op_t + parse_args(const value_t& args, + const keep_details_t& what_to_keep = keep_details_t(), + bool multiple_args = true, + bool subexpression = false) { if (! parser) - parser = parser_t(args, multiple_args); - ptr = parser->parse(); // expr_t::ptr + parser = parser_t(args, what_to_keep, multiple_args); + return parser->parse(subexpression); } - void parse_again() { - assert(parser); - ptr = parser->parse(); // expr_t::ptr + bool has_predicate(const kind_t& id) const { + return parser && parser->query_map.find(id) != parser->query_map.end(); + } + predicate_t get_predicate(const kind_t& id) const { + if (parser) { + query_map_t::const_iterator i = parser->query_map.find(id); + if (i != parser->query_map.end()) + return (*i).second; + } + return predicate_t(); } bool tokens_remaining() { return parser && parser->tokens_remaining(); } - - virtual string text() { - return print_to_str(); - } }; } // namespace ledger -- cgit v1.2.3 From e8e28c794bfb12fe1c9562c5bd124688ce45fc8e Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Tue, 22 Jun 2010 21:56:19 -0400 Subject: Added report query modifiers: for, since, until Now instead of ledger reg expense -p "this month", you can say: ledger reg expense for this month And as a shorthand for "for until this month", you can just say "until this month" or "since this month". --- src/precmd.cc | 10 ++++---- src/query.cc | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- src/query.h | 24 ++++++++++++++------ src/report.cc | 70 +++++++++++++++++++++++++++++++------------------------- src/report.h | 1 + 5 files changed, 131 insertions(+), 47 deletions(-) (limited to 'src/query.h') diff --git a/src/precmd.cc b/src/precmd.cc index d9977ab6..8ca27fd8 100644 --- a/src/precmd.cc +++ b/src/precmd.cc @@ -202,21 +202,19 @@ value_t query_command(call_scope_t& args) query_t query(args.value(), report.what_to_keep(), ! report.HANDLED(collapse)); - if (query.has_predicate(query_t::QUERY_LIMIT)) { + if (query.has_query(query_t::QUERY_LIMIT)) { call_scope_t sub_args(static_cast(args)); - sub_args.push_back - (string_value(query.get_predicate(query_t::QUERY_LIMIT).print_to_str())); + sub_args.push_back(string_value(query.get_query(query_t::QUERY_LIMIT))); parse_command(sub_args); } - if (query.has_predicate(query_t::QUERY_SHOW)) { + if (query.has_query(query_t::QUERY_SHOW)) { out << std::endl << _("====== Display predicate ======") << std::endl << std::endl; call_scope_t disp_sub_args(static_cast(args)); - disp_sub_args.push_back - (string_value(query.get_predicate(query_t::QUERY_SHOW).print_to_str())); + disp_sub_args.push_back(string_value(query.get_query(query_t::QUERY_SHOW))); parse_command(disp_sub_args); } diff --git a/src/query.cc b/src/query.cc index 43e3ffed..bed6afae 100644 --- a/src/query.cc +++ b/src/query.cc @@ -55,6 +55,10 @@ query_t::lexer_t::token_t query_t::lexer_t::next_token() resume: switch (*arg_i) { + case '\0': + assert(false); + break; + case '\'': case '"': case '/': { @@ -85,12 +89,17 @@ query_t::lexer_t::token_t query_t::lexer_t::next_token() if (multiple_args && consume_next_arg) { consume_next_arg = false; token_t tok(token_t::TERM, string(arg_i, arg_end)); + prev_arg_i = arg_i; arg_i = arg_end; return tok; } bool consume_next = false; switch (*arg_i) { + case '\0': + assert(false); + break; + case ' ': case '\t': case '\r': @@ -121,6 +130,10 @@ query_t::lexer_t::token_t query_t::lexer_t::next_token() string::const_iterator beg = arg_i; for (; arg_i != arg_end; ++arg_i) { switch (*arg_i) { + case '\0': + assert(false); + break; + case ' ': case '\t': case '\n': @@ -130,6 +143,7 @@ query_t::lexer_t::token_t query_t::lexer_t::next_token() else ident.push_back(*arg_i); break; + case '(': case ')': case '&': @@ -174,6 +188,12 @@ test_ident: return token_t(token_t::TOK_SHOW); else if (ident == "bold") return token_t(token_t::TOK_BOLD); + else if (ident == "for") + return token_t(token_t::TOK_FOR); + else if (ident == "since") + return token_t(token_t::TOK_SINCE); + else if (ident == "until") + return token_t(token_t::TOK_UNTIL); else if (ident == "expr") { // The expr keyword takes the whole of the next string as its argument. consume_next_arg = true; @@ -230,6 +250,9 @@ query_t::parser_t::parse_query_term(query_t::lexer_t::token_t::kind_t tok_contex switch (tok.kind) { case lexer_t::token_t::TOK_SHOW: case lexer_t::token_t::TOK_BOLD: + case lexer_t::token_t::TOK_FOR: + case lexer_t::token_t::TOK_SINCE: + case lexer_t::token_t::TOK_UNTIL: case lexer_t::token_t::END_REACHED: lexer.push_token(tok); break; @@ -423,7 +446,8 @@ query_t::parser_t::parse_query_expr(lexer_t::token_t::kind_t tok_context, if (! subexpression) { if (limiter) query_map.insert - (query_map_t::value_type(QUERY_LIMIT, predicate_t(limiter, what_to_keep))); + (query_map_t::value_type + (QUERY_LIMIT, predicate_t(limiter, what_to_keep).print_to_str())); lexer_t::token_t tok = lexer.peek_token(); while (tok.kind != lexer_t::token_t::END_REACHED) { @@ -445,7 +469,8 @@ query_t::parser_t::parse_query_expr(lexer_t::token_t::kind_t tok_context, if (node) query_map.insert - (query_map_t::value_type(QUERY_SHOW, predicate_t(node, what_to_keep))); + (query_map_t::value_type + (QUERY_SHOW, predicate_t(node, what_to_keep).print_to_str())); break; } @@ -462,7 +487,49 @@ query_t::parser_t::parse_query_expr(lexer_t::token_t::kind_t tok_context, if (node) query_map.insert - (query_map_t::value_type(QUERY_BOLD, predicate_t(node, what_to_keep))); + (query_map_t::value_type + (QUERY_BOLD, predicate_t(node, what_to_keep).print_to_str())); + break; + } + + case lexer_t::token_t::TOK_FOR: + case lexer_t::token_t::TOK_SINCE: + case lexer_t::token_t::TOK_UNTIL: { + tok = lexer.next_token(); + + string for_string; + + if (tok.kind == lexer_t::token_t::TOK_SINCE) + for_string = "since"; + else if (tok.kind == lexer_t::token_t::TOK_UNTIL) + for_string = "until"; + + lexer.consume_next_arg = true; + tok = lexer.peek_token(); + + while (tok.kind != lexer_t::token_t::END_REACHED) { + tok = lexer.next_token(); + assert(tok.kind == lexer_t::token_t::TERM); + + if (*tok.value == "show" || *tok.value == "bold" || + *tok.value == "for" || *tok.value == "since" || + *tok.value == "until") { + lexer.token_cache = lexer_t::token_t(); + lexer.arg_i = lexer.prev_arg_i; + lexer.consume_next_arg = false; + break; + } + + if (! for_string.empty()) + for_string += " "; + for_string += *tok.value; + + lexer.consume_next_arg = true; + tok = lexer.peek_token(); + } + + if (! for_string.empty()) + query_map.insert(query_map_t::value_type(QUERY_FOR, for_string)); break; } diff --git a/src/query.h b/src/query.h index e953206a..5a4651a0 100644 --- a/src/query.h +++ b/src/query.h @@ -60,6 +60,7 @@ public: value_t::sequence_t::const_iterator begin; value_t::sequence_t::const_iterator end; + string::const_iterator prev_arg_i; string::const_iterator arg_i; string::const_iterator arg_end; @@ -90,6 +91,9 @@ public: TOK_SHOW, TOK_BOLD, + TOK_FOR, + TOK_SINCE, + TOK_UNTIL, TERM, @@ -141,6 +145,9 @@ public: case TOK_EXPR: return "TOK_EXPR"; case TOK_SHOW: return "TOK_SHOW"; case TOK_BOLD: return "TOK_BOLD"; + case TOK_FOR: return "TOK_FOR"; + case TOK_SINCE: return "TOK_SINCE"; + case TOK_UNTIL: return "TOK_UNTIL"; case TERM: return string("TERM(") + *value + ")"; case END_REACHED: return "END_REACHED"; } @@ -164,6 +171,9 @@ public: case TOK_EXPR: return "expr"; case TOK_SHOW: return "show"; case TOK_BOLD: return "bold"; + case TOK_FOR: return "for"; + case TOK_SINCE: return "since"; + case TOK_UNTIL: return "until"; case END_REACHED: return ""; @@ -201,8 +211,7 @@ public: arg_i(lexer.arg_i), arg_end(lexer.arg_end), consume_whitespace(lexer.consume_whitespace), consume_next_arg(lexer.consume_next_arg), - multiple_args(lexer.multiple_args), - token_cache(lexer.token_cache) + multiple_args(lexer.multiple_args), token_cache(lexer.token_cache) { TRACE_CTOR(query_t::lexer_t, "copy"); } @@ -225,10 +234,11 @@ public: enum kind_t { QUERY_LIMIT, QUERY_SHOW, - QUERY_BOLD + QUERY_BOLD, + QUERY_FOR }; - typedef std::map query_map_t; + typedef std::map query_map_t; protected: class parser_t @@ -315,16 +325,16 @@ public: return parser->parse(subexpression); } - bool has_predicate(const kind_t& id) const { + bool has_query(const kind_t& id) const { return parser && parser->query_map.find(id) != parser->query_map.end(); } - predicate_t get_predicate(const kind_t& id) const { + string get_query(const kind_t& id) const { if (parser) { query_map_t::const_iterator i = parser->query_map.find(id); if (i != parser->query_map.end()) return (*i).second; } - return predicate_t(); + return empty_string; } bool tokens_remaining() { diff --git a/src/report.cc b/src/report.cc index 4a7ed2e5..9d733674 100644 --- a/src/report.cc +++ b/src/report.cc @@ -145,26 +145,8 @@ void report_t::normalize_options(const string& verb) // using -b or -e). Then, if no _duration_ was specified (such as monthly), // then ignore the period since the begin/end are the only interesting // details. - if (HANDLED(period_)) { - date_interval_t interval(HANDLER(period_).str()); - - optional begin = interval.begin(); - optional end = interval.end(); - - if (! HANDLED(begin_) && begin) { - string predicate = "date>=[" + to_iso_extended_string(*begin) + "]"; - HANDLER(limit_).on(string("?normalize"), predicate); - } - if (! HANDLED(end_) && end) { - string predicate = "date<[" + to_iso_extended_string(*end) + "]"; - HANDLER(limit_).on(string("?normalize"), predicate); - } - - if (! interval.duration) - HANDLER(period_).off(); - else if (! HANDLED(sort_all_)) - HANDLER(sort_xacts_).on_only(string("?normalize")); - } + if (HANDLED(period_)) + normalize_period(); // If -j or -J were specified, set the appropriate format string now so as // to avoid option ordering issues were we to have done it during the @@ -254,26 +236,52 @@ void report_t::normalize_options(const string& verb) } } +void report_t::normalize_period() +{ + date_interval_t interval(HANDLER(period_).str()); + + optional begin = interval.begin(); + optional end = interval.end(); + + if (! HANDLED(begin_) && begin) { + string predicate = "date>=[" + to_iso_extended_string(*begin) + "]"; + HANDLER(limit_).on(string("?normalize"), predicate); + } + if (! HANDLED(end_) && end) { + string predicate = "date<[" + to_iso_extended_string(*end) + "]"; + HANDLER(limit_).on(string("?normalize"), predicate); + } + + if (! interval.duration) + HANDLER(period_).off(); + else if (! HANDLED(sort_all_)) + HANDLER(sort_xacts_).on_only(string("?normalize")); +} + void report_t::parse_query_args(const value_t& args, const string& whence) { query_t query(args, what_to_keep()); - if (query.has_predicate(query_t::QUERY_LIMIT)) { - HANDLER(limit_) - .on(whence, query.get_predicate(query_t::QUERY_LIMIT).print_to_str()); - DEBUG("report.predicate", "Predicate = " << HANDLER(limit_).str()); + if (query.has_query(query_t::QUERY_LIMIT)) { + HANDLER(limit_).on(whence, query.get_query(query_t::QUERY_LIMIT)); + DEBUG("report.predicate", "Limit predicate = " << HANDLER(limit_).str()); } - if (query.has_predicate(query_t::QUERY_SHOW)) { - HANDLER(display_) - .on(whence, query.get_predicate(query_t::QUERY_SHOW).print_to_str()); + if (query.has_query(query_t::QUERY_SHOW)) { + HANDLER(display_).on(whence, query.get_query(query_t::QUERY_SHOW)); DEBUG("report.predicate", "Display predicate = " << HANDLER(display_).str()); } - if (query.has_predicate(query_t::QUERY_BOLD)) { - HANDLER(bold_if_) - .set_expr(whence, query.get_predicate(query_t::QUERY_BOLD).print_to_str()); - DEBUG("report.predicate", "Bolding predicate = " << HANDLER(display_).str()); + if (query.has_query(query_t::QUERY_BOLD)) { + HANDLER(bold_if_).set_expr(whence, query.get_query(query_t::QUERY_BOLD)); + DEBUG("report.predicate", "Bolding predicate = " << HANDLER(bold_if_).str()); + } + + if (query.has_query(query_t::QUERY_FOR)) { + HANDLER(period_).on(whence, query.get_query(query_t::QUERY_FOR)); + DEBUG("report.predicate", "Report period = " << HANDLER(period_).str()); + + normalize_period(); // it needs normalization } } diff --git a/src/report.h b/src/report.h index fc2fc1a2..59a632e6 100644 --- a/src/report.h +++ b/src/report.h @@ -126,6 +126,7 @@ public: } void normalize_options(const string& verb); + void normalize_period(); void parse_query_args(const value_t& args, const string& whence); void posts_report(post_handler_ptr handler); -- cgit v1.2.3