Added elision styles.

This commit is contained in:
John Wiegley 2006-03-28 18:27:12 +00:00
parent 5a93d4819e
commit f9b874e1cb
9 changed files with 179 additions and 57 deletions

63
NEWS
View file

@ -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.

View file

@ -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);
}

View file

@ -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 {

View file

@ -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;
};

View file

@ -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 },

View file

@ -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);

View file

@ -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 =

View file

@ -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;

View file

@ -42,7 +42,6 @@ struct value_expr_t
// Constants
CONSTANT,
ARG_INDEX,
ZERO,
CONSTANTS,