*** empty log message ***
This commit is contained in:
parent
30f79b0761
commit
2964dd15b2
9 changed files with 174 additions and 48 deletions
23
NEWS
23
NEWS
|
|
@ -21,10 +21,22 @@
|
|||
C 1.00 Gb = 1024 Mb
|
||||
C 1.00 Tb = 1024 Gb
|
||||
|
||||
- Added --ansi reporting option, which shows negative values as red
|
||||
using ANSI terminal codes; --ansi-invert makes non-negative values
|
||||
red (which makes more sense for income and budget reports, for
|
||||
example).
|
||||
- Added --ansi reporting option, which shows negative values in the
|
||||
running total column of the register report as red, using ANSI
|
||||
terminal codes; --ansi-invert makes non-negative values red (which
|
||||
makes more sense for the income and budget reports).
|
||||
|
||||
The --ansi functionality is triggered by the format modifier "!",
|
||||
for example the register reports uses the following for the total
|
||||
(last) column:
|
||||
|
||||
%!12.80T
|
||||
|
||||
At the moment neither the balance report nor any of the other
|
||||
reports make use of the ! modifier, and so will not change color
|
||||
even if --ansi is used. However, you can modify these report format
|
||||
strings yourself in ~/.ledgerrc if you wish to see red coloring of
|
||||
negative sums in other places.
|
||||
|
||||
- Added --only predicate, which occurs during transaction processing
|
||||
between --limit and --display. Here is a summary of how the three
|
||||
|
|
@ -183,6 +195,9 @@
|
|||
|
||||
- Added a new "csv" command, for outputting results in CSV format.
|
||||
|
||||
- Ledger now expands ~ in file pathnames specified in environment
|
||||
variables, initialization files and journal files.
|
||||
|
||||
- Effective dates may now be specified for entries:
|
||||
|
||||
2004/10/03=2004/09/30 Credit card company
|
||||
|
|
|
|||
113
config.cc
113
config.cc
|
|
@ -15,6 +15,14 @@
|
|||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_REALPATH
|
||||
extern "C" char *realpath(const char *, char resolved_path[]);
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_GETPWUID) || defined(HAVE_GETPWNAM)
|
||||
#include <pwd.h>
|
||||
#endif
|
||||
|
||||
namespace ledger {
|
||||
|
||||
namespace {
|
||||
|
|
@ -50,6 +58,71 @@ namespace {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
std::string expand_path(const std::string& path)
|
||||
{
|
||||
if (path.length() == 0 || path[0] != '~')
|
||||
return path;
|
||||
|
||||
const char * pfx = NULL;
|
||||
std::string::size_type pos = path.find_first_of('/');
|
||||
|
||||
if (path.length() == 1 || pos == 1) {
|
||||
pfx = std::getenv("HOME");
|
||||
#ifdef HAVE_GETPWUID
|
||||
if (! pfx) {
|
||||
// Punt. We're trying to expand ~/, but HOME isn't set
|
||||
struct passwd * pw = getpwuid(getuid());
|
||||
if (pw)
|
||||
pfx = pw->pw_dir;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#ifdef HAVE_GETPWNAM
|
||||
else {
|
||||
std::string user(path, 1, pos == std::string::npos ?
|
||||
std::string::npos : pos - 1);
|
||||
struct passwd * pw = getpwnam(user.c_str());
|
||||
if (pw)
|
||||
pfx = pw->pw_dir;
|
||||
}
|
||||
#endif
|
||||
|
||||
// if we failed to find an expansion, return the path unchanged.
|
||||
|
||||
if (! pfx)
|
||||
return path;
|
||||
|
||||
std::string result(pfx);
|
||||
|
||||
if (pos == std::string::npos)
|
||||
return result;
|
||||
|
||||
if (result.length() == 0 || result[result.length() - 1] != '/')
|
||||
result += '/';
|
||||
|
||||
result += path.substr(pos + 1);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string resolve_path(const std::string& path)
|
||||
{
|
||||
std::string resolved;;
|
||||
if (path[0] == '~')
|
||||
resolved = expand_path(path);
|
||||
else
|
||||
resolved = path;
|
||||
|
||||
#ifdef HAVE_REALPATH
|
||||
char buf[PATH_MAX];
|
||||
::realpath(resolved.c_str(), buf);
|
||||
return std::string(buf);
|
||||
#else
|
||||
return resolved;
|
||||
#endif
|
||||
}
|
||||
|
||||
void config_t::reset()
|
||||
{
|
||||
ledger::amount_expr.reset(new value_expr("a"));
|
||||
|
|
@ -58,10 +131,10 @@ void config_t::reset()
|
|||
pricing_leeway = 24 * 3600;
|
||||
budget_flags = BUDGET_NO_BUDGET;
|
||||
balance_format = "%20T %2_%-a\n";
|
||||
register_format = ("%D %-.20P %-.22A %12.67t %12.80T\n%/"
|
||||
"%32|%-.22A %12.67t %12.80T\n");
|
||||
wide_register_format = ("%D %-.35P %-.38A %22.108t %22.132T\n%/"
|
||||
"%48|%-.38A %22.108t %22.132T\n");
|
||||
register_format = ("%D %-.20P %-.22A %12.67t %!12.80T\n%/"
|
||||
"%32|%-.22A %12.67t %!12.80T\n");
|
||||
wide_register_format = ("%D %-.35P %-.38A %22.108t %!22.132T\n%/"
|
||||
"%48|%-.38A %22.108t %!22.132T\n");
|
||||
csv_register_format = "\"%D\",\"%P\",\"%A\",\"%t\",\"%T\"\n";
|
||||
plot_amount_format = "%D %(S(t))\n";
|
||||
plot_total_format = "%D %(S(T))\n";
|
||||
|
|
@ -727,19 +800,29 @@ OPT_BEGIN(version, "v") {
|
|||
} OPT_END(version);
|
||||
|
||||
OPT_BEGIN(init_file, "i:") {
|
||||
config->init_file = optarg;
|
||||
std::string path = resolve_path(optarg);
|
||||
if (access(path.c_str(), R_OK) != -1)
|
||||
config->init_file = path;
|
||||
else
|
||||
throw new error(std::string("The init file '") + path +
|
||||
"' does not exist or is not readable");
|
||||
} OPT_END(init_file);
|
||||
|
||||
OPT_BEGIN(file, "f:") {
|
||||
if (std::string(optarg) == "-" || access(optarg, R_OK) != -1)
|
||||
if (std::string(optarg) == "-") {
|
||||
config->data_file = optarg;
|
||||
else
|
||||
throw new error(std::string("The ledger file '") + optarg +
|
||||
"' does not exist or is not readable");
|
||||
} else {
|
||||
std::string path = resolve_path(optarg);
|
||||
if (access(path.c_str(), R_OK) != -1)
|
||||
config->data_file = path;
|
||||
else
|
||||
throw new error(std::string("The ledger file '") + path +
|
||||
"' does not exist or is not readable");
|
||||
}
|
||||
} OPT_END(file);
|
||||
|
||||
OPT_BEGIN(cache, ":") {
|
||||
config->cache_file = optarg;
|
||||
config->cache_file = resolve_path(optarg);
|
||||
} OPT_END(cache);
|
||||
|
||||
OPT_BEGIN(no_cache, "") {
|
||||
|
|
@ -747,8 +830,14 @@ OPT_BEGIN(no_cache, "") {
|
|||
} OPT_END(no_cache);
|
||||
|
||||
OPT_BEGIN(output, "o:") {
|
||||
if (std::string(optarg) != "-")
|
||||
config->output_file = optarg;
|
||||
if (std::string(optarg) != "-") {
|
||||
std::string path = resolve_path(optarg);
|
||||
if (access(path.c_str(), W_OK) != -1)
|
||||
config->output_file = path;
|
||||
else
|
||||
throw new error(std::string("The output file '") + path +
|
||||
"' is not writable");
|
||||
}
|
||||
} OPT_END(output);
|
||||
|
||||
OPT_BEGIN(account, "a:") {
|
||||
|
|
|
|||
2
config.h
2
config.h
|
|
@ -117,6 +117,8 @@ void option_help(std::ostream& out);
|
|||
|
||||
#define OPT_END(tag)
|
||||
|
||||
std::string resolve_path(const std::string& path);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
void trace(const std::string& cat, const std::string& str);
|
||||
|
|
|
|||
|
|
@ -274,7 +274,7 @@ AC_STRUCT_TM
|
|||
# Checks for library functions.
|
||||
#AC_FUNC_ERROR_AT_LINE
|
||||
AC_HEADER_STDC
|
||||
AC_CHECK_FUNCS([access mktime realpath stat strftime strptime])
|
||||
AC_CHECK_FUNCS([access mktime realpath stat strftime strptime getpwuid getpwnam])
|
||||
|
||||
AC_CONFIG_FILES([Makefile])
|
||||
AC_OUTPUT
|
||||
|
|
|
|||
23
format.cc
23
format.cc
|
|
@ -118,8 +118,15 @@ element_t * format_t::parse_elements(const std::string& fmt)
|
|||
}
|
||||
|
||||
++p;
|
||||
if (*p == '-') {
|
||||
current->align_left = true;
|
||||
while (*p == '!' || *p == '-') {
|
||||
switch (*p) {
|
||||
case '-':
|
||||
current->flags |= ELEMENT_ALIGN_LEFT;
|
||||
break;
|
||||
case '!':
|
||||
current->flags |= ELEMENT_HIGHLIGHT;
|
||||
break;
|
||||
}
|
||||
++p;
|
||||
}
|
||||
|
||||
|
|
@ -273,7 +280,7 @@ namespace {
|
|||
out.width(0);
|
||||
out << "\e[31m";
|
||||
|
||||
if (elem->align_left)
|
||||
if (elem->flags & ELEMENT_ALIGN_LEFT)
|
||||
out << std::left;
|
||||
else
|
||||
out << std::right;
|
||||
|
|
@ -294,7 +301,7 @@ void format_t::format(std::ostream& out_str, const details_t& details) const
|
|||
std::string name;
|
||||
bool ignore_max_width = false;
|
||||
|
||||
if (elem->align_left)
|
||||
if (elem->flags & ELEMENT_ALIGN_LEFT)
|
||||
out << std::left;
|
||||
else
|
||||
out << std::right;
|
||||
|
|
@ -347,7 +354,7 @@ void format_t::format(std::ostream& out_str, const details_t& details) const
|
|||
break;
|
||||
|
||||
case value_t::INTEGER:
|
||||
if (ansi_codes) {
|
||||
if (ansi_codes && elem->flags & ELEMENT_HIGHLIGHT) {
|
||||
if (ansi_invert) {
|
||||
if (*((long *) value.data) > 0)
|
||||
mark_red(out, elem);
|
||||
|
|
@ -364,7 +371,7 @@ void format_t::format(std::ostream& out_str, const details_t& details) const
|
|||
break;
|
||||
|
||||
case value_t::AMOUNT:
|
||||
if (ansi_codes) {
|
||||
if (ansi_codes && elem->flags & ELEMENT_HIGHLIGHT) {
|
||||
if (ansi_invert) {
|
||||
if (*((amount_t *) value.data) > 0)
|
||||
mark_red(out, elem);
|
||||
|
|
@ -384,7 +391,7 @@ void format_t::format(std::ostream& out_str, const details_t& details) const
|
|||
if (! bal)
|
||||
bal = &((balance_pair_t *) value.data)->quantity;
|
||||
|
||||
if (ansi_codes) {
|
||||
if (ansi_codes && elem->flags & ELEMENT_HIGHLIGHT) {
|
||||
if (ansi_invert) {
|
||||
if (*bal > 0)
|
||||
mark_red(out, elem);
|
||||
|
|
@ -404,7 +411,7 @@ void format_t::format(std::ostream& out_str, const details_t& details) const
|
|||
break;
|
||||
}
|
||||
|
||||
if (ansi_codes)
|
||||
if (ansi_codes && elem->flags & ELEMENT_HIGHLIGHT)
|
||||
mark_plain(out);
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
21
format.h
21
format.h
|
|
@ -13,6 +13,9 @@ std::string truncated(const std::string& str, unsigned int width,
|
|||
std::string partial_account_name(const account_t& account,
|
||||
const unsigned int start_depth);
|
||||
|
||||
#define ELEMENT_ALIGN_LEFT 0x01
|
||||
#define ELEMENT_HIGHLIGHT 0x02
|
||||
|
||||
struct element_t
|
||||
{
|
||||
enum kind_t {
|
||||
|
|
@ -45,18 +48,18 @@ struct element_t
|
|||
DEPTH_SPACER
|
||||
};
|
||||
|
||||
bool align_left;
|
||||
unsigned int min_width;
|
||||
unsigned int max_width;
|
||||
|
||||
kind_t type;
|
||||
std::string chars;
|
||||
value_expr * val_expr;
|
||||
kind_t type;
|
||||
unsigned char flags;
|
||||
std::string chars;
|
||||
unsigned char min_width;
|
||||
unsigned char max_width;
|
||||
value_expr * val_expr;
|
||||
|
||||
struct element_t * next;
|
||||
|
||||
element_t() : align_left(false), min_width(0), max_width(0),
|
||||
type(STRING), val_expr(NULL), next(NULL) {
|
||||
element_t() : type(STRING), flags(false),
|
||||
min_width(0), max_width(0),
|
||||
val_expr(NULL), next(NULL) {
|
||||
DEBUG_PRINT("ledger.memory.ctors", "ctor element_t");
|
||||
}
|
||||
|
||||
|
|
|
|||
13
textual.cc
13
textual.cc
|
|
@ -21,7 +21,7 @@
|
|||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
#if defined(__GNUG__) && __GNUG__ < 3
|
||||
#ifdef HAVE_REALPATH
|
||||
extern "C" char *realpath(const char *, char resolved_path[]);
|
||||
#endif
|
||||
|
||||
|
|
@ -737,13 +737,15 @@ unsigned int textual_parser_t::parse(std::istream& in,
|
|||
push_var<unsigned int> save_linenum(linenum);
|
||||
|
||||
path = p;
|
||||
if (path[0] != '/' && path[0] != '\\') {
|
||||
if (path[0] != '/' && path[0] != '\\' && path[0] != '~') {
|
||||
std::string::size_type pos = save_path.prev.rfind('/');
|
||||
if (pos == std::string::npos)
|
||||
pos = save_path.prev.rfind('\\');
|
||||
if (pos != std::string::npos)
|
||||
path = std::string(save_path.prev, 0, pos + 1) + path;
|
||||
}
|
||||
path = resolve_path(path);
|
||||
|
||||
DEBUG_PRINT("ledger.textual.include", "line " << linenum << ": " <<
|
||||
"Including path '" << path << "'");
|
||||
|
||||
|
|
@ -783,7 +785,7 @@ unsigned int textual_parser_t::parse(std::istream& in,
|
|||
else if (word == "def") {
|
||||
if (! global_scope.get())
|
||||
init_value_expr();
|
||||
value_auto_ptr expr(parse_boolean_expr(p, global_scope.get()));
|
||||
parse_value_definition(p);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -856,10 +858,11 @@ void write_textual_journal(journal_t& journal, std::string path,
|
|||
{
|
||||
unsigned long index = 0;
|
||||
std::string found;
|
||||
char buf1[PATH_MAX];
|
||||
char buf2[PATH_MAX];
|
||||
|
||||
#ifdef HAVE_REALPATH
|
||||
char buf1[PATH_MAX];
|
||||
char buf2[PATH_MAX];
|
||||
|
||||
::realpath(path.c_str(), buf1);
|
||||
for (strings_list::iterator i = journal.sources.begin();
|
||||
i != journal.sources.end();
|
||||
|
|
|
|||
19
valexpr.cc
19
valexpr.cc
|
|
@ -1375,10 +1375,11 @@ void init_value_expr()
|
|||
node->left->constant_i = 2;
|
||||
node->set_right(new value_expr_t(value_expr_t::F_VALUE));
|
||||
globals->define("P", node);
|
||||
value_auto_ptr val(parse_boolean_expr("value=P(t,m)", globals));
|
||||
value_auto_ptr tval(parse_boolean_expr("total_value=P(T,m)", globals));
|
||||
value_auto_ptr valof(parse_boolean_expr("valueof(x)=P(x,m)", globals));
|
||||
value_auto_ptr dvalof(parse_boolean_expr("datedvalueof(x,y)=P(x,y)", globals));
|
||||
|
||||
parse_value_definition("value=P(t,m)", globals);
|
||||
parse_value_definition("total_value=P(T,m)", globals);
|
||||
parse_value_definition("valueof(x)=P(x,m)", globals);
|
||||
parse_value_definition("datedvalueof(x,y)=P(x,y)", globals);
|
||||
|
||||
node = new value_expr_t(value_expr_t::O_DEF);
|
||||
node->set_left(new value_expr_t(value_expr_t::CONSTANT_I));
|
||||
|
|
@ -1416,9 +1417,9 @@ void init_value_expr()
|
|||
node->set_right(new value_expr_t(value_expr_t::F_DAY));
|
||||
globals->define("dayof", node);
|
||||
|
||||
value_auto_ptr year(parse_boolean_expr("year=yearof(d)", globals));
|
||||
value_auto_ptr month(parse_boolean_expr("month=monthof(d)", globals));
|
||||
value_auto_ptr day(parse_boolean_expr("day=dayof(d)", globals));
|
||||
parse_value_definition("year=yearof(d)", globals);
|
||||
parse_value_definition("month=monthof(d)", globals);
|
||||
parse_value_definition("day=dayof(d)", globals);
|
||||
|
||||
// Macros
|
||||
node = parse_value_expr("P(a,d)");
|
||||
|
|
@ -1437,8 +1438,8 @@ void init_value_expr()
|
|||
globals->define("G", node);
|
||||
globals->define("total_gain", node);
|
||||
|
||||
value_auto_ptr minx(parse_boolean_expr("min(x,y)=x<y?x:y", globals));
|
||||
value_auto_ptr maxx(parse_boolean_expr("max(x,y)=x>y?x:y", globals));
|
||||
parse_value_definition("min(x,y)=x<y?x:y", globals);
|
||||
parse_value_definition("max(x,y)=x>y?x:y", globals);
|
||||
}
|
||||
|
||||
value_expr_t * parse_value_expr(std::istream& in, scope_t * scope,
|
||||
|
|
|
|||
|
|
@ -460,6 +460,12 @@ inline value_t compute_total(const details_t& details = details_t()) {
|
|||
return total_expr->compute(details);
|
||||
}
|
||||
|
||||
inline void parse_value_definition(const std::string& str,
|
||||
scope_t * scope = NULL) {
|
||||
value_auto_ptr expr
|
||||
(parse_boolean_expr(str, scope ? scope : global_scope.get()));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename T>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue