diff options
-rw-r--r-- | NEWS | 63 | ||||
-rw-r--r-- | datetime.cc | 14 | ||||
-rw-r--r-- | format.cc | 82 | ||||
-rw-r--r-- | format.h | 13 | ||||
-rw-r--r-- | option.cc | 28 | ||||
-rw-r--r-- | option.h | 2 | ||||
-rw-r--r-- | report.cc | 2 | ||||
-rw-r--r-- | valexpr.cc | 31 | ||||
-rw-r--r-- | valexpr.h | 1 |
9 files changed, 179 insertions, 57 deletions
@@ -1,8 +1,43 @@ - Ledger NEWS * 3.0 +- The style for eliding long account names (for example, in the + register report) has been changed. Previously Ledger would elide + the end of long names, replacing the excess length with "..". + However, in some cases this caused the base account name to be + missing from the report! + + What Ledger now does is that if an account name is too long, it will + start abbreviating the first parts of the account name down to two + letters in length. If this results in a string that is still too + long, the front will be elided -- not the end. For example: + + Expenses:Cash ; OK, not too long + Ex:Wednesday:Cash ; "Expenses" was abbreviated to fit + Ex:We:Afternoon:Cash ; "Expenses" and "Wednesday" abbreviated + ; Expenses:Wednesday:Afternoon:Lunch:Snack:Candy:Chocolate:Cash + ..:Af:Lu:Sn:Ca:Ch:Cash ; Abbreviated and elided! + + As you can see, it now takes a very deep account name before any + elision will occur, whereas in 2.x elisions were fairly common. + +- In addition to the new elision change mentioned above, the style is + also configurable: + + --truncate leading ; elide at the beginning + --truncate middle ; elide in the middle + --truncate trailing ; elide at end (Ledger 2.x's behavior) + --truncate abbrev ; the new behavior + + --abbrev-len 2 ; set length of abbreviations + + These elision styles affect all format strings which have a maximum + width, so they will also affect the payee in a register report, for + example. In the case of non-account names, "abbrev" is equivalent + to "trailing", even though it elides at the beginning for long + account names. + - Error reporting has been greatly improving, now showing full contextual information for most error messages. @@ -59,7 +94,7 @@ monthly costs report, for example, because it makes the following command possible: - ledger -M --only "a>100" reg ^Expenses:Food + ledger -M --only "a>100" reg ^Expenses:Food This shows only *months* whose amount is greater than 100. If --limit had been used, it would have been a monthly summary of @@ -71,7 +106,7 @@ This predicate does not constrain calculation, but only display. Consider the same command as above: - ledger -M --display "a>100" reg ^Expenses:Food + ledger -M --display "a>100" reg ^Expenses:Food This displays only lines whose amount is greater than 100, *yet the running total still includes amounts from all transactions*. @@ -79,7 +114,7 @@ the current month's checking register while still giving a correct ending balance: - ledger --display "d>[this month]" reg Checking + ledger --display "d>[this month]" reg Checking Note that these predicates can be combined. Here is a report that considers only food bills whose individual cost is greater than @@ -88,8 +123,8 @@ retain an accurate running total with respect to the entire ledger file: - ledger -M --limit "a>20" --only "a>200" \ - --display "year == yearof([last year])" reg ^Expenses:Food + ledger -M --limit "a>20" --only "a>200" \ + --display "year == yearof([last year])" reg ^Expenses:Food - Added new "--descend AMOUNT" and "--descend-if VALEXPR" reporting options. For any reports that display valued transactions (i.e., @@ -166,12 +201,12 @@ G gain_total U(x) abs(x) S(x) quant(x), quantity(x) - comm(x), commodity(x) - setcomm(x,y), set_commodity(x,y) + comm(x), commodity(x) + setcomm(x,y), set_commodity(x,y) A(x) mean(x), avg(x), average(x) P(x,y) val(x,y), value(x,y) - min(x,y) - max(x,y) + min(x,y) + max(x,y) - There are new "parse" and "expr" commands, whose argument is a single value expression. Ledger will simply print out the result of @@ -318,10 +353,10 @@ the following is now supported, which wasn't previously: 2004/06/21 Adjustment - Retirement 100 FUNDA - Retirement 200 FUNDB - Retirement 300 FUNDC - Equity:Adjustments + Retirement 100 FUNDA + Retirement 200 FUNDB + Retirement 300 FUNDC + Equity:Adjustments - Fixed several bugs relating to QIF parsing, budgeting and forecasting. diff --git a/datetime.cc b/datetime.cc index d8668cb8..2e47c554 100644 --- a/datetime.cc +++ b/datetime.cc @@ -253,6 +253,12 @@ void interval_t::parse(std::istream& in) months = 3 * quantity; else if (word == "years") years = quantity; + else if (word == "hours") + hours = quantity; + else if (word == "minutes") + minutes = quantity; + else if (word == "seconds") + seconds = quantity; } else if (word == "day") days = 1; @@ -264,6 +270,12 @@ void interval_t::parse(std::istream& in) months = 3; else if (word == "year") years = 1; + else if (word == "hour") + hours = 1; + else if (word == "minute") + minutes = 1; + else if (word == "second") + seconds = 1; } else if (word == "daily") days = 1; @@ -279,6 +291,8 @@ void interval_t::parse(std::istream& in) months = 3; else if (word == "yearly") years = 1; + else if (word == "hourly") + hours = 1; else if (word == "this" || word == "last" || word == "next") { parse_date_words(in, word, &begin, &end); } @@ -6,29 +6,32 @@ namespace ledger { +format_t::elision_style_t format_t::elision_style = ABBREVIATE; +int format_t::abbrev_length = 2; + bool format_t::ansi_codes = false; bool format_t::ansi_invert = false; -std::string truncated(const std::string& str, unsigned int width, - const int style) +std::string format_t::truncate(const std::string& str, unsigned int width, + const bool is_account) { const int len = str.length(); if (len <= width) return str; - assert(width < 254); + assert(width < 4095); - char buf[256]; + char buf[4096]; - switch (style) { - case 0: + switch (elision_style) { + case TRUNCATE_LEADING: // This method truncates at the beginning. std::strncpy(buf, str.c_str() + (len - width), width); buf[0] = '.'; buf[1] = '.'; break; - case 1: + case TRUNCATE_MIDDLE: // This method truncates in the middle. std::strncpy(buf, str.c_str(), width / 2); std::strncpy(buf + width / 2, @@ -38,7 +41,52 @@ std::string truncated(const std::string& str, unsigned int width, buf[width / 2] = '.'; break; - case 2: + case ABBREVIATE: + if (is_account) { + std::list<std::string> parts; + std::string::size_type beg = 0; + for (std::string::size_type pos = str.find(':'); + pos != std::string::npos; + beg = pos + 1, pos = str.find(':', beg)) + parts.push_back(std::string(str, beg, pos - beg)); + parts.push_back(std::string(str, beg)); + + std::string result; + int newlen = len; + for (std::list<std::string>::iterator i = parts.begin(); + i != parts.end(); + i++) { + // Don't contract the last element + std::list<std::string>::iterator x = i; + if (++x == parts.end()) { + result += *i; + break; + } + + if (newlen > width) { + result += std::string(*i, 0, abbrev_length); + result += ":"; + newlen -= (*i).length() - abbrev_length; + } else { + result += *i; + result += ":"; + } + } + + if (newlen > width) { + // Even abbreviated its too big to show the last account, so + // abbreviate all but the last and truncate at the beginning. + std::strncpy(buf, result.c_str() + (result.length() - width), width); + buf[0] = '.'; + buf[1] = '.'; + } else { + std::strcpy(buf, result.c_str()); + } + break; + } + // fall through... + + case TRUNCATE_TRAILING: // This method truncates at the end (the default). std::strncpy(buf, str.c_str(), width - 2); buf[width - 2] = '.'; @@ -543,7 +591,7 @@ void format_t::format(std::ostream& out_str, const details_t& details) const char buf[256]; std::strftime(buf, 255, elem->chars.c_str(), date.localtime()); - out << (elem->max_width == 0 ? buf : truncated(buf, elem->max_width)); + out << (elem->max_width == 0 ? buf : truncate(buf, elem->max_width)); break; } @@ -572,9 +620,9 @@ void format_t::format(std::ostream& out_str, const details_t& details) const std::strcat(buf, "="); std::strcat(buf, ebuf); - out << (elem->max_width == 0 ? buf : truncated(buf, elem->max_width)); + out << (elem->max_width == 0 ? buf : truncate(buf, elem->max_width)); } else { - out << (elem->max_width == 0 ? abuf : truncated(abuf, elem->max_width)); + out << (elem->max_width == 0 ? abuf : truncate(abuf, elem->max_width)); } break; } @@ -621,8 +669,8 @@ void format_t::format(std::ostream& out_str, const details_t& details) const case element_t::PAYEE: if (details.entry) out << (elem->max_width == 0 ? - details.entry->payee : truncated(details.entry->payee, - elem->max_width)); + details.entry->payee : truncate(details.entry->payee, + elem->max_width)); break; case element_t::OPT_NOTE: @@ -633,8 +681,8 @@ void format_t::format(std::ostream& out_str, const details_t& details) const case element_t::NOTE: if (details.xact) out << (elem->max_width == 0 ? - details.xact->note : truncated(details.xact->note, - elem->max_width)); + details.xact->note : truncate(details.xact->note, + elem->max_width)); break; case element_t::OPT_ACCOUNT: @@ -661,7 +709,7 @@ void format_t::format(std::ostream& out_str, const details_t& details) const if (details.xact && details.xact->flags & TRANSACTION_VIRTUAL) { if (elem->max_width > 2) - name = truncated(name, elem->max_width - 2); + name = truncate(name, elem->max_width - 2, true); if (details.xact->flags & TRANSACTION_BALANCE) name = "[" + name + "]"; @@ -669,7 +717,7 @@ void format_t::format(std::ostream& out_str, const details_t& details) const name = "(" + name + ")"; } else if (elem->max_width > 0) - name = truncated(name, elem->max_width); + name = truncate(name, elem->max_width, true); out << name; } else { @@ -73,6 +73,16 @@ struct format_t std::string format_string; element_t * elements; + enum elision_style_t { + TRUNCATE_TRAILING, + TRUNCATE_MIDDLE, + TRUNCATE_LEADING, + ABBREVIATE + }; + + static elision_style_t elision_style; + static int abbrev_length; + static bool ansi_codes; static bool ansi_invert; @@ -97,6 +107,9 @@ struct format_t static element_t * parse_elements(const std::string& fmt); + static std::string truncate(const std::string& str, unsigned int width, + const bool is_account = false); + void format(std::ostream& out, const details_t& details) const; }; @@ -38,9 +38,9 @@ namespace { if ((result = (int)name[0] - (int)array[mid].long_opt[0]) == 0) result = std::strcmp(name, array[mid].long_opt); - if (result > 0) + if (result > 0) first = mid + 1; // repeat search in top half. - else if (result < 0) + else if (result < 0) last = mid - 1; // repeat search in bottom half. else return &array[mid]; @@ -102,7 +102,7 @@ void process_arguments(option_t * options, int argc, char ** argv, opt = search_options(options, name); if (! opt) throw new option_error(std::string("illegal option --") + name); - + if (opt->wants_arg && ! value) { value = *++i; if (! value) @@ -610,6 +610,22 @@ OPT_BEGIN(pager, ":") { config->pager = optarg; } OPT_END(pager); +OPT_BEGIN(truncate, ":") { + std::string style(optarg); + if (style == "leading") + format_t::elision_style = format_t::TRUNCATE_LEADING; + else if (style == "middle") + format_t::elision_style = format_t::TRUNCATE_MIDDLE; + else if (style == "trailing") + format_t::elision_style = format_t::TRUNCATE_TRAILING; + else if (style == "abbrev") + format_t::elision_style = format_t::ABBREVIATE; +} OPT_END(truncate); + +OPT_BEGIN(abbrev_len, ":") { + format_t::abbrev_length = std::atoi(optarg); +} OPT_END(abbrev_len); + OPT_BEGIN(empty, "E") { report->show_empty = true; } OPT_END(empty); @@ -658,7 +674,7 @@ OPT_BEGIN(descend, "") { pos != std::string::npos; beg = pos + 1, pos = arg.find(';', beg)) report->descend_expr += (std::string("t=={") + - std::string(arg, beg, pos) + "};"); + std::string(arg, beg, pos - beg) + "};"); report->descend_expr += (std::string("t=={") + std::string(arg, beg) + "}"); } OPT_END(descend); @@ -895,7 +911,7 @@ OPT_BEGIN(set_price, ":") { for (std::string::size_type pos = arg.find(';'); pos != std::string::npos; beg = pos + 1, pos = arg.find(';', beg)) - parse_price_setting(std::string(arg, beg, pos).c_str()); + parse_price_setting(std::string(arg, beg, pos - beg).c_str()); parse_price_setting(std::string(arg, beg).c_str()); } OPT_END(set_price); @@ -940,6 +956,7 @@ OPT_BEGIN(percentage, "%") { ////////////////////////////////////////////////////////////////////// option_t config_options[CONFIG_OPTIONS_SIZE] = { + { "abbrev-len", '\0', true, opt_abbrev_len, false }, { "account", 'a', true, opt_account, false }, { "actual", 'L', false, opt_actual, false }, { "add-budget", '\0', false, opt_add_budget, false }, @@ -1025,6 +1042,7 @@ option_t config_options[CONFIG_OPTIONS_SIZE] = { { "total-data", 'J', false, opt_total_data, false }, { "totals", '\0', false, opt_totals, false }, { "trace", '\0', false, opt_trace, false }, + { "truncate", '\0', true, opt_truncate, false }, { "unbudgeted", '\0', false, opt_unbudgeted, false }, { "uncleared", 'U', false, opt_uncleared, false }, { "verbose", '\0', false, opt_verbose, false }, @@ -38,7 +38,7 @@ class report_t; extern config_t * config; extern report_t * report; -#define CONFIG_OPTIONS_SIZE 95 +#define CONFIG_OPTIONS_SIZE 97 extern option_t config_options[CONFIG_OPTIONS_SIZE]; void option_help(std::ostream& out); @@ -260,7 +260,7 @@ report_t::chain_xact_handlers(const std::string& command, for (std::string::size_type pos = descend_expr.find(';'); pos != std::string::npos; beg = pos + 1, pos = descend_expr.find(';', beg)) - descend_exprs.push_back(std::string(descend_expr, beg, pos)); + descend_exprs.push_back(std::string(descend_expr, beg, pos - beg)); descend_exprs.push_back(std::string(descend_expr, beg)); for (std::list<std::string>::reverse_iterator i = @@ -137,10 +137,6 @@ void value_expr_t::compute(value_t& result, const details_t& details, { try { switch (kind) { - case ZERO: - result = 0L; - break; - case ARG_INDEX: throw new compute_error("Cannot directly compute an arg_index"); @@ -589,7 +585,8 @@ void value_expr_t::compute(value_t& result, const details_t& details, break; case O_DEF: - throw new compute_error("Cannot compute function definition"); + result = 0L; + break; case O_REF: { assert(left); @@ -869,7 +866,7 @@ value_expr_t * parse_value_term(std::istream& in, scope_t * scope, node->left->arg_index = arg_index++; params->define(ident, node.release()); } - + if (peek_next_nonws(in) != '=') { in.get(c); unexpected(c, '='); @@ -886,11 +883,8 @@ value_expr_t * parse_value_term(std::istream& in, scope_t * scope, node->set_left(new value_expr_t(value_expr_t::ARG_INDEX)); node->left->arg_index = arg_index; node->set_right(def.release()); - - scope->define(buf, node.release()); - // Returning a dummy value in place of the definition - node.reset(new value_expr_t(value_expr_t::ZERO)); + scope->define(buf, node.get()); } else { assert(scope); value_expr_t * def = scope->lookup(buf); @@ -1573,10 +1567,8 @@ bool write_value_expr(std::ostream& out, std::string symbol; switch (node->kind) { - case value_expr_t::ZERO: - out << '0'; - break; case value_expr_t::ARG_INDEX: + out << node->arg_index; break; case value_expr_t::CONSTANT: @@ -1712,7 +1704,13 @@ bool write_value_expr(std::ostream& out, out << "@arg" << node->arg_index; break; case value_expr_t::O_DEF: - out << "O_DEF"; + out << "<def args=\""; + if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) + found = true; + out << "\" value=\""; + if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) + found = true; + out << "\">"; break; case value_expr_t::O_REF: @@ -1875,7 +1873,7 @@ bool write_value_expr(std::ostream& out, if (end_pos && node == node_to_find) *end_pos = (long)out.tellp() - 1; - + return found; } @@ -1890,9 +1888,6 @@ void dump_value_expr(std::ostream& out, const value_expr_t * node, out << " "; switch (node->kind) { - case value_expr_t::ZERO: - out << "ZERO"; - break; case value_expr_t::ARG_INDEX: out << "ARG_INDEX - " << node->arg_index; break; @@ -42,7 +42,6 @@ struct value_expr_t // Constants CONSTANT, ARG_INDEX, - ZERO, CONSTANTS, |