diff options
author | John Wiegley <johnw@newartisans.com> | 2009-01-30 00:40:46 -0400 |
---|---|---|
committer | John Wiegley <johnw@newartisans.com> | 2009-01-30 00:40:46 -0400 |
commit | cdb123974cd6657d4e8d47af9aef5a80d49f2810 (patch) | |
tree | 6bb28be7dee3adab341dd12f5e07f303155585e7 | |
parent | c96635fe605f142f0402e5728e5d2a49b48c1453 (diff) | |
download | fork-ledger-cdb123974cd6657d4e8d47af9aef5a80d49f2810.tar.gz fork-ledger-cdb123974cd6657d4e8d47af9aef5a80d49f2810.tar.bz2 fork-ledger-cdb123974cd6657d4e8d47af9aef5a80d49f2810.zip |
Created a new stream.h file for dealing with output streaming.
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | src/main.cc | 200 | ||||
-rw-r--r-- | src/output.cc | 14 | ||||
-rw-r--r-- | src/output.h | 2 | ||||
-rw-r--r-- | src/report.cc | 183 | ||||
-rw-r--r-- | src/report.h | 107 | ||||
-rw-r--r-- | src/stream.cc | 142 | ||||
-rw-r--r-- | src/stream.h | 118 | ||||
-rw-r--r-- | src/utils.h | 1 | ||||
-rw-r--r-- | src/xml.cc | 2 | ||||
-rw-r--r-- | src/xml.h | 6 |
11 files changed, 534 insertions, 243 deletions
diff --git a/Makefile.am b/Makefile.am index 1b4df29d..2e0e0136 100644 --- a/Makefile.am +++ b/Makefile.am @@ -31,6 +31,7 @@ libledger_util_la_SOURCES = \ src/utils.cc \ src/times.cc \ src/mask.cc \ + src/stream.cc \ src/binary.cc \ lib/sha1.cpp @@ -116,6 +117,7 @@ pkginclude_HEADERS = \ src/error.h \ src/times.h \ src/mask.h \ + src/stream.h \ src/binary.h \ \ src/amount.h \ diff --git a/src/main.cc b/src/main.cc index 2f51a5bf..dfbeeb75 100644 --- a/src/main.cc +++ b/src/main.cc @@ -45,12 +45,6 @@ #include "ofx.h" #endif -#ifdef HAVE_UNIX_PIPES -#include <sys/types.h> -#include <sys/wait.h> -#include "fdstream.h" -#endif - namespace ledger { int read_and_report(ledger::report_t& report, int argc, char * argv[], char * envp[]) @@ -133,176 +127,58 @@ namespace ledger { TRACE_FINISH(arguments, 1); - // Configure the output stream - -#ifdef HAVE_UNIX_PIPES - int status, pfd[2]; // Pipe file descriptors -#endif - report.output_stream = &std::cout; - - if (report.output_file) { - report.output_stream = new ofstream(*report.output_file); - } -#ifdef HAVE_UNIX_PIPES - else if (report.pager) { - status = pipe(pfd); - if (status == -1) - throw_(std::logic_error, "Failed to create pipe"); - - status = fork(); - if (status < 0) { - throw_(std::logic_error, "Failed to fork child process"); - } - else if (status == 0) { // child - // Duplicate pipe's reading end into stdin - status = dup2(pfd[0], STDIN_FILENO); - if (status == -1) - perror("dup2"); - - // Close unuseful file descriptors: the pipe's writing and - // reading ends (the latter is not needed anymore, after the - // duplication). - close(pfd[1]); - close(pfd[0]); - - // Find command name: its the substring starting right of the - // rightmost '/' character in the pager pathname. See manpage - // for strrchr. - execlp(report.pager->native_file_string().c_str(), - basename(*report.pager).c_str(), NULL); - perror("execl"); - exit(1); - } - else { // parent - close(pfd[0]); - report.output_stream = new boost::fdostream(pfd[1]); - } - } -#endif - // Read the command word and see if it's any of the debugging commands // that Ledger supports. - std::ostream& out(*report.output_stream); - string verb = *arg++; - if (verb == "parse") { - out << "--- Input text ---" << std::endl; - out << *arg << std::endl; - - out << std::endl << "--- Text as parsed ---" << std::endl; - expr_t expr(*arg); - expr.print(out); - out << std::endl; - - out << std::endl << "--- Expression tree ---" << std::endl; - expr.dump(out); - - out << std::endl << "--- Calculated value ---" << std::endl; - expr.calc(report).print(out); - out << std::endl; - - return 0; - } - else if (verb == "compile") { - out << "--- Input text ---" << std::endl; - out << *arg << std::endl; - - out << std::endl << "--- Text as parsed ---" << std::endl; - expr_t expr(*arg); - expr.print(out); - out << std::endl; - - out << std::endl << "--- Expression tree ---" << std::endl; - expr.dump(out); - - expr.compile(report); - - out << std::endl << "--- Compiled tree ---" << std::endl; - expr.dump(out); - - out << std::endl << "--- Calculated value ---" << std::endl; - expr.calc(report).print(out); - out << std::endl; - - return 0; - } - else if (verb == "eval") { - expr_t expr(*arg); - out << expr.calc(report).strip_annotations() << std::endl; - return 0; - } - else if (verb == "format") { - format_t fmt(*arg); - fmt.dump(out); - return 0; - } - else if (verb == "period") { - interval_t interval(*arg); - - if (! is_valid(interval.begin)) { - out << "Time period has no beginning." << std::endl; - } else { - out << "begin: " << format_date(interval.begin) << std::endl; - out << " end: " << format_date(interval.end) << std::endl; - out << std::endl; - - date_t date = interval.first(); - - for (int i = 0; i < 20; i++) { - out << std::right; - out.width(2); - - out << i << ": " << format_date(date) << std::endl; - - date = interval.increment(date); - if (is_valid(interval.end) && date >= interval.end) - break; - } - } - return 0; - } + function_t command; + if (expr_t::ptr_op_t def = report.lookup(string("ledger_precmd_") + verb)) + command = def->as_function(); // Parse the initialization file, which can only be textual; then - // parse the journal data. + // parse the journal data. But only do this if there was no + // "pre-command", which are always executed without doing any + // parsing. - INFO_START(journal, "Read journal file"); + if (! command) { + INFO_START(journal, "Read journal file"); - journal_t& journal(*session.create_journal()); + journal_t& journal(*session.create_journal()); - std::size_t count = session.read_data(journal, report.account); - if (count == 0) - throw_(parse_error, "Failed to locate any journal entries; " - "did you specify a valid file with -f?"); + std::size_t count = session.read_data(journal, report.account); + if (count == 0) + throw_(parse_error, "Failed to locate any journal entries; " + "did you specify a valid file with -f?"); - INFO_FINISH(journal); + INFO_FINISH(journal); - INFO("Found " << count << " entries"); + INFO("Found " << count << " entries"); - TRACE_FINISH(entry_text, 1); - TRACE_FINISH(entry_date, 1); - TRACE_FINISH(entry_details, 1); - TRACE_FINISH(entry_xacts, 1); - TRACE_FINISH(entries, 1); - TRACE_FINISH(parsing_total, 1); + TRACE_FINISH(entry_text, 1); + TRACE_FINISH(entry_details, 1); + TRACE_FINISH(entry_xacts, 1); + TRACE_FINISH(entries, 1); + TRACE_FINISH(session_parser, 1); + TRACE_FINISH(parsing_total, 1); - // Lookup the command object corresponding to the command verb. + // Lookup the command object corresponding to the command verb. - function_t command; - if (expr_t::ptr_op_t def = report.lookup(string("cmd_") + verb)) - command = def->as_function(); + if (expr_t::ptr_op_t def = report.lookup(string("ledger_cmd_") + verb)) + command = def->as_function(); + } if (! command) throw_(std::logic_error, string("Unrecognized command '") + verb + "'"); +#if 1 // Patch up some of the reporting options based on what kind of // command it was. // jww (2008-08-14): This code really needs to be rationalized away // for 3.0. - if (verb[0] == 'p' || verb == "entry" || verb == "dump") { + if (verb == "print" || verb == "entry" || verb == "dump") { report.show_related = true; report.show_all_related = true; } @@ -340,6 +216,7 @@ namespace ledger { report.display_predicate = "amount"; } } +#endif // Now setup the various formatting strings @@ -357,6 +234,10 @@ namespace ledger { if (! report.report_period.empty() && ! report.sort_all) report.entry_sort = true; + // Setup the output stream, which might involve invoking the pager + + report.output_stream.initialize(report.output_file, report.pager_path); + // Create an argument scope containing the report command's // arguments, and then invoke the command. @@ -384,23 +265,6 @@ namespace ledger { } #endif - // If the user specified a pager, wait for it to exit now - -#ifdef HAVE_UNIX_PIPES - if (! report.output_file && report.pager) { - checked_delete(report.output_stream); - close(pfd[1]); - - // Wait for child to finish - wait(&status); - if (status & 0xffff != 0) - throw_(std::logic_error, "Something went wrong in the pager"); - } -#endif - else if (DO_VERIFY() && report.output_file) { - checked_delete(report.output_stream); - } - return 0; } } diff --git a/src/output.cc b/src/output.cc index 372981ab..c24bf111 100644 --- a/src/output.cc +++ b/src/output.cc @@ -57,7 +57,7 @@ format_xacts::format_xacts(report_t& _report, const string& format) void format_xacts::operator()(xact_t& xact) { - std::ostream& out(*report.output_stream); + std::ostream& out(report.output_stream); if (! xact.has_xdata() || ! xact.xdata().has_flags(XACT_EXT_DISPLAYED)) { @@ -82,7 +82,7 @@ void format_xacts::operator()(xact_t& xact) void format_entries::format_last_entry() { bool first = true; - std::ostream& out(*report.output_stream); + std::ostream& out(report.output_stream); foreach (xact_t * xact, last_entry->xacts) { if (xact->has_xdata() && @@ -144,7 +144,7 @@ void print_entry(std::ostream& out, const entry_base_t& entry_base, void format_accounts::flush() { - std::ostream& out(*report.output_stream); + std::ostream& out(report.output_stream); if (print_final_total) { assert(out); @@ -168,7 +168,7 @@ void format_accounts::operator()(account_t& account) if (! account.parent) { account.xdata().add_flags(ACCOUNT_EXT_TO_DISPLAY); } else { - format.format(*report.output_stream, account); + format.format(report.output_stream, account); account.xdata().add_flags(ACCOUNT_EXT_DISPLAYED); } } @@ -246,7 +246,7 @@ format_equity::format_equity(report_t& _report, const string& _format) entry_t header_entry; header_entry.payee = "Opening Balances"; header_entry._date = current_date; - first_line_format.format(*report.output_stream, header_entry); + first_line_format.format(report.output_stream, header_entry); } void format_equity::flush() @@ -254,7 +254,7 @@ void format_equity::flush() account_t summary(NULL, "Equity:Opening Balances"); account_t::xdata_t& xdata(summary.xdata()); - std::ostream& out(*report.output_stream); + std::ostream& out(report.output_stream); xdata.value = total.negate(); @@ -280,7 +280,7 @@ void format_equity::flush() void format_equity::operator()(account_t& account) { - std::ostream& out(*report.output_stream); + std::ostream& out(report.output_stream); if (display_account(account)) { if (account.has_xdata()) { diff --git a/src/output.h b/src/output.h index 4b23cabb..0d4e2072 100644 --- a/src/output.h +++ b/src/output.h @@ -55,7 +55,7 @@ public: } virtual void flush() { - report.output_stream->flush(); + report.output_stream.flush(); } virtual void operator()(xact_t& xact); }; diff --git a/src/report.cc b/src/report.cc index 4b45577b..2c34fc5d 100644 --- a/src/report.cc +++ b/src/report.cc @@ -441,7 +441,7 @@ namespace { template <class Type = xact_t, class handler_ptr = xact_handler_ptr, void (report_t::*report_method)(handler_ptr) = - &report_t::xacts_report> + &report_t::xacts_report> class reporter { shared_ptr<item_handler<Type> > handler; @@ -464,6 +464,143 @@ namespace { return true; } }; + + value_t parse_command(call_scope_t& args) + { + var_t<string> arg(args, 0); + + if (! arg) { + throw std::logic_error("Usage: parse TEXT"); + return 1L; + } + + report_t& report(find_scope<report_t>(args)); + std::ostream& out(report.output_stream); + + out << "--- Input text ---" << std::endl; + out << *arg << std::endl; + + out << std::endl << "--- Text as parsed ---" << std::endl; + expr_t expr(*arg); + expr.print(out); + out << std::endl; + + out << std::endl << "--- Expression tree ---" << std::endl; + expr.dump(out); + + out << std::endl << "--- Calculated value ---" << std::endl; + expr.calc(args).print(out); + out << std::endl; + + return 0L; + } + + value_t compile_command(call_scope_t& args) + { + var_t<string> arg(args, 0); + + if (! arg) { + throw std::logic_error("Usage: compile TEXT"); + return 1L; + } + + report_t& report(find_scope<report_t>(args)); + std::ostream& out(report.output_stream); + + out << "--- Input text ---" << std::endl; + out << *arg << std::endl; + + out << std::endl << "--- Text as parsed ---" << std::endl; + expr_t expr(*arg); + expr.print(out); + out << std::endl; + + out << std::endl << "--- Expression tree ---" << std::endl; + expr.dump(out); + + expr.compile(args); + + out << std::endl << "--- Compiled tree ---" << std::endl; + expr.dump(out); + + out << std::endl << "--- Calculated value ---" << std::endl; + expr.calc(args).print(out); + out << std::endl; + + return 0L; + } + + value_t eval_command(call_scope_t& args) + { + var_t<string> arg(args, 0); + + if (! arg) { + throw std::logic_error("Usage: eval TEXT"); + return 1L; + } + + report_t& report(find_scope<report_t>(args)); + std::ostream& out(report.output_stream); + + expr_t expr(*arg); + out << expr.calc(args).strip_annotations() << std::endl; + return 0L; + } + + value_t format_command(call_scope_t& args) + { + var_t<string> arg(args, 0); + + if (! arg) { + throw std::logic_error("Usage: format TEXT"); + return 1L; + } + + report_t& report(find_scope<report_t>(args)); + std::ostream& out(report.output_stream); + + format_t fmt(*arg); + fmt.dump(out); + + return 0L; + } + + value_t period_command(call_scope_t& args) + { + var_t<string> arg(args, 0); + + if (! arg) { + throw std::logic_error("Usage: period TEXT"); + return 1L; + } + + report_t& report(find_scope<report_t>(args)); + std::ostream& out(report.output_stream); + + interval_t interval(*arg); + + if (! is_valid(interval.begin)) { + out << "Time period has no beginning." << std::endl; + } else { + out << "begin: " << format_date(interval.begin) << std::endl; + out << " end: " << format_date(interval.end) << std::endl; + out << std::endl; + + date_t date = interval.first(); + + for (int i = 0; i < 20; i++) { + out << std::right; + out.width(2); + + out << i << ": " << format_date(date) << std::endl; + + date = interval.increment(date); + if (is_valid(interval.end) && date >= interval.end) + break; + } + } + return 0L; + } } expr_t::ptr_op_t report_t::lookup(const string& name) @@ -475,8 +612,13 @@ expr_t::ptr_op_t report_t::lookup(const string& name) return MAKE_FUNCTOR(report_t::get_amount_expr); break; - case 'c': - if (std::strncmp(p, "cmd_", 4) == 0) { + case 'd': + if (std::strcmp(p, "display_total") == 0) + return MAKE_FUNCTOR(report_t::get_display_total); + break; + + case 'l': + if (std::strncmp(p, "ledger_cmd_", 11) == 0) { #define FORMAT(str) \ (format_string.empty() ? session. str : format_string) @@ -494,7 +636,7 @@ expr_t::ptr_op_t report_t::lookup(const string& name) // xml #endif - p = p + 4; + p = p + 11; switch (*p) { case 'b': if (*(p + 1) == '\0' || @@ -528,11 +670,6 @@ expr_t::ptr_op_t report_t::lookup(const string& name) } break; - case 'd': - if (std::strcmp(p, "display_total") == 0) - return MAKE_FUNCTOR(report_t::get_display_total); - break; - case 'm': if (std::strcmp(p, "market_value") == 0) return MAKE_FUNCTOR(report_t::f_market_value); @@ -782,7 +919,33 @@ expr_t::ptr_op_t report_t::lookup(const string& name) break; case 'p': - if (std::strcmp(p, "print_balance") == 0) + if (std::strncmp(p, "ledger_precmd_", 14) == 0) { + p = p + 14; + switch (*p) { + case 'p': + if (std::strcmp(p, "parse") == 0) + return WRAP_FUNCTOR(parse_command); + else if (std::strcmp(p, "period") == 0) + return WRAP_FUNCTOR(period_command); + break; + + case 'c': + if (std::strcmp(p, "compile") == 0) + return WRAP_FUNCTOR(compile_command); + break; + + case 'e': + if (std::strcmp(p, "eval") == 0) + return WRAP_FUNCTOR(eval_command); + break; + + case 'f': + if (std::strcmp(p, "format") == 0) + return WRAP_FUNCTOR(format_command); + break; + } + } + else if (std::strcmp(p, "print_balance") == 0) return WRAP_FUNCTOR(print_balance); break; diff --git a/src/report.h b/src/report.h index 59380949..6fcdc3f7 100644 --- a/src/report.h +++ b/src/report.h @@ -86,57 +86,57 @@ class report_t : public noncopyable, public scope_t report_t(); public: - optional<path> output_file; - std::ostream * output_stream; - - string format_string; - string date_output_format; - string predicate; - string secondary_predicate; - string display_predicate; - string report_period; - string report_period_sort; - string sort_string; - string descend_expr; - string forecast_limit; - string reconcile_balance; - string reconcile_date; - - expr_t amount_expr; - expr_t total_expr; - expr_t display_total; - - unsigned long budget_flags; - - long head_entries; - long tail_entries; - - bool show_collapsed; - bool show_subtotal; - bool show_totals; - bool show_related; - bool show_all_related; - bool show_inverted; - bool show_empty; - bool days_of_the_week; - bool by_payee; - bool comm_as_payee; - bool code_as_payee; - bool show_revalued; - bool show_revalued_only; - bool keep_price; - bool keep_date; - bool keep_tag; - bool entry_sort; - bool sort_all; - bool anonymize; - - string account; - optional<path> pager; - - bool raw_mode; - - session_t& session; + optional<path> output_file; + output_stream_t output_stream; + + string format_string; + string date_output_format; + string predicate; + string secondary_predicate; + string display_predicate; + string report_period; + string report_period_sort; + string sort_string; + string descend_expr; + string forecast_limit; + string reconcile_balance; + string reconcile_date; + + expr_t amount_expr; + expr_t total_expr; + expr_t display_total; + + unsigned long budget_flags; + + long head_entries; + long tail_entries; + + bool show_collapsed; + bool show_subtotal; + bool show_totals; + bool show_related; + bool show_all_related; + bool show_inverted; + bool show_empty; + bool days_of_the_week; + bool by_payee; + bool comm_as_payee; + bool code_as_payee; + bool show_revalued; + bool show_revalued_only; + bool keep_price; + bool keep_date; + bool keep_tag; + bool entry_sort; + bool sort_all; + bool anonymize; + + string account; + optional<path> pager_path; + + bool raw_mode; + + session_t& session; explicit report_t(session_t& _session) : amount_expr("amount"), @@ -426,11 +426,12 @@ public: return true; } -#if 0 value_t option_pager(call_scope_t& args) { // : - config->pager = optarg; + pager_path = args[0].as_string(); + return true; } +#if 0 value_t option_truncate(call_scope_t& args) { // : std::string style(optarg); if (style == "leading") diff --git a/src/stream.cc b/src/stream.cc new file mode 100644 index 00000000..0394c042 --- /dev/null +++ b/src/stream.cc @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2003-2009, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "utils.h" + +#ifdef HAVE_UNIX_PIPES +#include <sys/types.h> +#include <sys/wait.h> +#include "fdstream.h" +#endif + +namespace ledger { + +namespace { + /** + * @brief Forks a child process so that Ledger may handle running a + * pager + * + * In order for the pager option to work, Ledger has to run the pager + * itself, which requires Ledger to fork a new process in order to run + * the pager. This function does the necessary fork. After the fork, + * two processes exist. One of them is exec'd to create the pager; + * the other is still Ledger. + * + * This function returns only for the process that is still Ledger. + * + * @param out Pointer to a pointer to the output stream. This The + * pointer to the output stream is changed so that the stream is + * connected to the stdin of the pager. The caller is responsible for + * cleaning this up. + * + * @param pager_path Path to the pager command. + * + * @return The file descriptor of the pipe to the pager. The caller + * is responsible for cleaning this up. + * + * @exception std::logic_error Some problem was encountered, such as + * failure to create a pipe or failure to fork a child process. + */ + int do_fork(std::ostream ** os, const path& pager_path) + { + int pfd[2]; + + int status = pipe(pfd); + if (status == -1) + throw std::logic_error("Failed to create pipe"); + + status = fork(); + if (status < 0) { + throw std::logic_error("Failed to fork child process"); + } + else if (status == 0) { // child + const char *arg0; + + // Duplicate pipe's reading end into stdin + status = dup2(pfd[0], STDIN_FILENO); + if (status == -1) + perror("dup2"); + + // Close unuseful file descriptors: the pipe's writing and reading + // ends (the latter is not needed anymore, after the duplication). + close(pfd[1]); + close(pfd[0]); + + // Find command name: its the substring starting right of the + // rightmost '/' character in the pager pathname. See manpage for + // strrchr. + path basename(pager_path.leaf()); + execlp(pager_path.string().c_str(), basename.string().c_str(), + (char *)0); + + // We should never, ever reach here + perror("execl"); + exit(1); + } + else { // parent + close(pfd[0]); + *os = new boost::fdostream(pfd[1]); + } + return pfd[1]; + } +} + +void output_stream_t::initialize(const optional<path>& output_file, + const optional<path>& pager_path) +{ + TRACE_CTOR(output_stream_t, "const path&, const path&"); + + if (output_file) + os = new ofstream(*output_file); + else if (pager_path && exists(*pager_path)) + pipe_to_pager_fd = do_fork(&os, *pager_path); + else + os = &std::cout; +} + +output_stream_t::~output_stream_t() +{ + TRACE_DTOR(output_stream_t); + + if (os && os != &std::cout) + checked_delete(os); + + if (pipe_to_pager_fd != -1) { + close(pipe_to_pager_fd); + + int status; + wait(&status); + if (status & 0xffff != 0) + throw std::logic_error("Something went wrong in the pager"); + } +} + +} // namespace ledger diff --git a/src/stream.h b/src/stream.h new file mode 100644 index 00000000..3c8364b0 --- /dev/null +++ b/src/stream.h @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2003-2009, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @addtogroup util + */ + +/** + * @file stream.h + * @author John Wiegley, Omari Norman + * + * @ingroup util + * + * @brief A utility class for abstracting an output stream. + * + * Because Ledger might send output to a file, the console, or a pager + * child process, different cleanup is needed for each scenario. This + * file abstracts those various needs. + */ + +#ifndef _STREAM_H +#define _STREAM_H + +namespace ledger { + +/** + * @brief An output stream + * + * A stream to output in Ledger may be going to one of three places: + * to stdout, to a file, or to a pager. Construct an output_stream_t and + * the stream will automatically be cleaned up upon destruction. + * + * This class suffers from "else-if-heimer's disease," see Marshall + * Cline's "C++ FAQ Lite". Arguably this should be three different + * classes, but that introduces additional unneeded complications. + */ +class output_stream_t +{ +private: + int pipe_to_pager_fd; + +public: + /** + * A pointer to the ostream. Don't delete this; the output_stream_t + * class takes care of this. + */ + std::ostream * os; + + /** + * Construct a new output_stream_t. + * + * @param output_file File to which to send output. If both this + * and pager are set, output_file takes priority. + * + * @param pager Path to a pager. To not use a pager, leave this + * empty. + */ + output_stream_t() : pipe_to_pager_fd(-1), os(NULL) { + TRACE_CTOR(output_stream_t, ""); + } + + void initialize(const optional<path>& output_file = none, + const optional<path>& pager_path = none); + + /** + * Destroys an output_stream_t. This deletes the dynamically + * allocated ostream, if necessary. It also closes output file + * descriptor, if necessary. + */ + ~output_stream_t(); + + /** + * Convertor to a standard ostream. This is used so that we can + * stream directly to an object of type output_stream_t. + */ + operator std::ostream&() { + return *os; + } + + /** + * Flushing function. A simple proxy for ostream's flush. + */ + void flush() { + os->flush(); + } +}; + +} // namespace ledger + +#endif // _STREAM_H diff --git a/src/utils.h b/src/utils.h index 248b59df..16bcd09d 100644 --- a/src/utils.h +++ b/src/utils.h @@ -471,6 +471,7 @@ void finish_timer(const char * name); #include "error.h" #include "times.h" #include "flags.h" +#include "stream.h" #include "pushvar.h" /** @@ -400,7 +400,7 @@ void output_xml_string(std::ostream& out, const string& str) void format_xml_entries::format_last_entry() { - std::ostream& out(*report.output_stream); + std::ostream& out(report.output_stream); #if 0 // jww (2008-05-08): Need to format these dates @@ -93,8 +93,8 @@ public: const bool _show_totals = false) : format_entries(_report, ""), show_totals(_show_totals) { TRACE_CTOR(format_xml_entries, "std::ostream&, const bool"); - *report.output_stream << "<?xml version=\"1.0\"?>\n" - << "<ledger version=\"2.5\">\n"; + report.output_stream << "<?xml version=\"1.0\"?>\n" + << "<ledger version=\"2.5\">\n"; } virtual ~format_xml_entries() throw() { TRACE_DTOR(format_xml_entries); @@ -102,7 +102,7 @@ public: virtual void flush() { format_entries::flush(); - *report.output_stream << "</ledger>" << std::endl; + report.output_stream << "</ledger>" << std::endl; } virtual void format_last_entry(); |