summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2006-03-15 11:54:18 +0000
committerJohn Wiegley <johnw@newartisans.com>2008-04-13 02:41:30 -0400
commit2964dd15b24787162c53560ae9ceae5a92cfc86d (patch)
treef3421c6773e460aa1c0b9e6d8e793b5efb88818d
parent30f79b07618872b7326742430e03795da5782860 (diff)
downloadfork-ledger-2964dd15b24787162c53560ae9ceae5a92cfc86d.tar.gz
fork-ledger-2964dd15b24787162c53560ae9ceae5a92cfc86d.tar.bz2
fork-ledger-2964dd15b24787162c53560ae9ceae5a92cfc86d.zip
*** empty log message ***
-rw-r--r--NEWS23
-rw-r--r--config.cc113
-rw-r--r--config.h2
-rw-r--r--configure.in2
-rw-r--r--format.cc23
-rw-r--r--format.h21
-rw-r--r--textual.cc13
-rw-r--r--valexpr.cc19
-rw-r--r--valexpr.h6
9 files changed, 174 insertions, 48 deletions
diff --git a/NEWS b/NEWS
index 0b0818d0..f9a10c2c 100644
--- a/NEWS
+++ b/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
diff --git a/config.cc b/config.cc
index c33a9429..9e9a7f16 100644
--- a/config.cc
+++ b/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:") {
diff --git a/config.h b/config.h
index 44a24976..4c6b2ced 100644
--- a/config.h
+++ b/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);
diff --git a/configure.in b/configure.in
index 444ba803..3e86a44c 100644
--- a/configure.in
+++ b/configure.in
@@ -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
diff --git a/format.cc b/format.cc
index 75d52733..e1248b41 100644
--- a/format.cc
+++ b/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;
}
diff --git a/format.h b/format.h
index 09e1d746..df9d2dcc 100644
--- a/format.h
+++ b/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");
}
diff --git a/textual.cc b/textual.cc
index cd0b9b28..29b548e5 100644
--- a/textual.cc
+++ b/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();
diff --git a/valexpr.cc b/valexpr.cc
index 22bd88d3..b3f7f80f 100644
--- a/valexpr.cc
+++ b/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,
diff --git a/valexpr.h b/valexpr.h
index 2ce57278..87367fb0 100644
--- a/valexpr.h
+++ b/valexpr.h
@@ -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>