240 lines
5.3 KiB
C++
240 lines
5.3 KiB
C++
#include "format.h"
|
|
#include "error.h"
|
|
|
|
namespace ledger {
|
|
|
|
std::string truncated(const std::string& str, unsigned int width)
|
|
{
|
|
char buf[256];
|
|
std::memset(buf, '\0', 255);
|
|
assert(width < 256);
|
|
std::strncpy(buf, str.c_str(), str.length());
|
|
if (buf[width])
|
|
std::strcpy(&buf[width - 2], "..");
|
|
return buf;
|
|
}
|
|
|
|
std::string maximal_account_name(const item_t * item, const item_t * parent)
|
|
{
|
|
std::string name = item->account->name;
|
|
for (const item_t * i = item->parent;
|
|
i && i->account && i != parent;
|
|
i = i->parent)
|
|
name = i->account->name + ":" + name;
|
|
return name;
|
|
}
|
|
|
|
node_t * format_t::value_expr = NULL;
|
|
node_t * format_t::total_expr = NULL;
|
|
|
|
element_t * format_t::parse_elements(const std::string& fmt)
|
|
{
|
|
element_t * result = NULL;
|
|
element_t * current = NULL;
|
|
std::string str;
|
|
|
|
for (const char * p = fmt.c_str(); *p; p++) {
|
|
if (*p == '%') {
|
|
if (! result) {
|
|
current = result = new element_t;
|
|
} else {
|
|
current->next = new element_t;
|
|
current = current->next;
|
|
}
|
|
|
|
if (! str.empty()) {
|
|
current->type = element_t::STRING;
|
|
current->chars = str;
|
|
str = "";
|
|
|
|
current->next = new element_t;
|
|
current = current->next;
|
|
}
|
|
|
|
++p;
|
|
if (*p == '-') {
|
|
current->align_left = true;
|
|
++p;
|
|
}
|
|
|
|
std::string num;
|
|
while (*p && std::isdigit(*p))
|
|
num += *p++;
|
|
if (! num.empty())
|
|
current->min_width = std::atol(num.c_str());
|
|
|
|
if (*p == '.') {
|
|
++p;
|
|
num = "";
|
|
while (*p && std::isdigit(*p))
|
|
num += *p++;
|
|
if (! num.empty()) {
|
|
current->max_width = std::atol(num.c_str());
|
|
if (current->min_width == 0)
|
|
current->min_width = current->max_width;
|
|
}
|
|
}
|
|
|
|
switch (*p) {
|
|
case '%':
|
|
current->type = element_t::STRING;
|
|
current->chars = "%";
|
|
break;
|
|
|
|
case '(':
|
|
++p;
|
|
num = "";
|
|
while (*p && *p != ')')
|
|
num += *p++;
|
|
if (*p != ')')
|
|
throw format_error("Missing ')'");
|
|
|
|
current->type = element_t::VALUE_EXPR;
|
|
current->val_expr = parse_expr(num);
|
|
break;
|
|
|
|
case '[':
|
|
++p;
|
|
num = "";
|
|
while (*p && *p != ']')
|
|
num += *p++;
|
|
if (*p != ']')
|
|
throw format_error("Missing ']'");
|
|
|
|
current->type = element_t::DATE_STRING;
|
|
current->chars = num;
|
|
break;
|
|
|
|
case 'd':
|
|
current->type = element_t::DATE_STRING;
|
|
current->chars = "%Y/%m/%d";
|
|
break;
|
|
|
|
case 'p': current->type = element_t::PAYEE; break;
|
|
case 'n': current->type = element_t::ACCOUNT_NAME; break;
|
|
case 'N': current->type = element_t::ACCOUNT_FULLNAME; break;
|
|
case 't': current->type = element_t::VALUE; break;
|
|
case 'T': current->type = element_t::TOTAL; break;
|
|
case '_': current->type = element_t::SPACER; break;
|
|
}
|
|
} else {
|
|
str += *p;
|
|
}
|
|
}
|
|
|
|
if (! str.empty()) {
|
|
if (! result) {
|
|
current = result = new element_t;
|
|
} else {
|
|
current->next = new element_t;
|
|
current = current->next;
|
|
}
|
|
current->type = element_t::STRING;
|
|
current->chars = str;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void format_t::format_elements(std::ostream& out, const item_t * item,
|
|
const item_t * displayed_parent) const
|
|
{
|
|
std::string result;
|
|
|
|
for (const element_t * elem = elements;
|
|
elem;
|
|
elem = elem->next) {
|
|
if (elem->align_left)
|
|
out << std::left;
|
|
else
|
|
out << std::right;
|
|
|
|
if (elem->min_width > 0)
|
|
out.width(elem->min_width);
|
|
|
|
switch (elem->type) {
|
|
case element_t::STRING:
|
|
out << elem->chars;;
|
|
break;
|
|
|
|
case element_t::VALUE_EXPR: {
|
|
balance_t value = elem->val_expr->compute(item);
|
|
value.write(out, elem->min_width,
|
|
elem->max_width > 0 ? elem->max_width : elem->min_width);
|
|
break;
|
|
}
|
|
|
|
case element_t::DATE_STRING:
|
|
if (item->date != -1) {
|
|
char buf[256];
|
|
std::strftime(buf, 255, elem->chars.c_str(), std::gmtime(&item->date));
|
|
out << (elem->max_width == 0 ? buf : truncated(buf, elem->max_width));
|
|
} else {
|
|
out << " ";
|
|
}
|
|
break;
|
|
|
|
case element_t::PAYEE:
|
|
out << (elem->max_width == 0 ?
|
|
item->payee : truncated(item->payee, elem->max_width));
|
|
break;
|
|
|
|
case element_t::ACCOUNT_NAME:
|
|
if (item->account) {
|
|
std::string name = maximal_account_name(item, displayed_parent);
|
|
out << (elem->max_width == 0 ? name : truncated(name, elem->max_width));
|
|
} else {
|
|
out << " ";
|
|
}
|
|
break;
|
|
|
|
case element_t::ACCOUNT_FULLNAME:
|
|
if (item->account)
|
|
out << (elem->max_width == 0 ?
|
|
item->account->fullname() :
|
|
truncated(item->account->fullname(), elem->max_width));
|
|
else
|
|
out << " ";
|
|
break;
|
|
|
|
case element_t::VALUE: {
|
|
balance_t value = compute_value(item);
|
|
value.write(out, elem->min_width,
|
|
elem->max_width > 0 ? elem->max_width : elem->min_width);
|
|
break;
|
|
}
|
|
|
|
case element_t::TOTAL: {
|
|
balance_t value = compute_total(item);
|
|
value.write(out, elem->min_width,
|
|
elem->max_width > 0 ? elem->max_width : elem->min_width);
|
|
break;
|
|
}
|
|
|
|
case element_t::SPACER: {
|
|
int depth = 0;
|
|
for (const item_t * i = item; i->parent; i = i->parent)
|
|
depth++;
|
|
|
|
for (const item_t * i = item->parent;
|
|
i && i->account && i != displayed_parent;
|
|
i = i->parent)
|
|
depth--;
|
|
|
|
while (--depth >= 0) {
|
|
if (elem->min_width > 0 || elem->max_width > 0)
|
|
out.width(elem->min_width > elem->max_width ?
|
|
elem->min_width : elem->max_width);
|
|
out << " ";
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace ledger
|