Added elision styles.
This commit is contained in:
parent
5a93d4819e
commit
f9b874e1cb
9 changed files with 179 additions and 57 deletions
63
NEWS
63
NEWS
|
|
@ -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.
|
||||
|
|
|
|||
14
datetime.cc
14
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);
|
||||
}
|
||||
|
|
|
|||
82
format.cc
82
format.cc
|
|
@ -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 {
|
||||
|
|
|
|||
13
format.h
13
format.h
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
28
option.cc
28
option.cc
|
|
@ -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 },
|
||||
|
|
|
|||
2
option.h
2
option.h
|
|
@ -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 =
|
||||
|
|
|
|||
31
valexpr.cc
31
valexpr.cc
|
|
@ -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,
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue