diff options
author | John Wiegley <johnw@newartisans.com> | 2006-03-15 11:54:18 +0000 |
---|---|---|
committer | John Wiegley <johnw@newartisans.com> | 2008-04-13 02:41:30 -0400 |
commit | 2964dd15b24787162c53560ae9ceae5a92cfc86d (patch) | |
tree | f3421c6773e460aa1c0b9e6d8e793b5efb88818d | |
parent | 30f79b07618872b7326742430e03795da5782860 (diff) | |
download | fork-ledger-2964dd15b24787162c53560ae9ceae5a92cfc86d.tar.gz fork-ledger-2964dd15b24787162c53560ae9ceae5a92cfc86d.tar.bz2 fork-ledger-2964dd15b24787162c53560ae9ceae5a92cfc86d.zip |
*** empty log message ***
-rw-r--r-- | NEWS | 23 | ||||
-rw-r--r-- | config.cc | 113 | ||||
-rw-r--r-- | config.h | 2 | ||||
-rw-r--r-- | configure.in | 2 | ||||
-rw-r--r-- | format.cc | 23 | ||||
-rw-r--r-- | format.h | 21 | ||||
-rw-r--r-- | textual.cc | 13 | ||||
-rw-r--r-- | valexpr.cc | 19 | ||||
-rw-r--r-- | valexpr.h | 6 |
9 files changed, 174 insertions, 48 deletions
@@ -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 @@ -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:") { @@ -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 @@ -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; } @@ -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"); } @@ -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(); @@ -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> |