1 /*
2  * Copyright (c) 2003-2018, John Wiegley.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  * - Redistributions of source code must retain the above copyright
9  *   notice, this list of conditions and the following disclaimer.
10  *
11  * - Redistributions in binary form must reproduce the above copyright
12  *   notice, this list of conditions and the following disclaimer in the
13  *   documentation and/or other materials provided with the distribution.
14  *
15  * - Neither the name of New Artisans LLC nor the names of its
16  *   contributors may be used to endorse or promote products derived from
17  *   this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 /**
33  * @addtogroup report
34  */
35 
36 /**
37  * @file   report.h
38  * @author John Wiegley
39  *
40  * @ingroup report
41  */
42 #ifndef _REPORT_H
43 #define _REPORT_H
44 
45 #include "expr.h"
46 #include "query.h"
47 #include "chain.h"
48 #include "stream.h"
49 #include "option.h"
50 #include "commodity.h"
51 #include "annotate.h"
52 #include "session.h"
53 #include "format.h"
54 
55 namespace ledger {
56 
57 class session_t;
58 class xact_t;
59 
60 // These are the elements of any report:
61 //
62 // 1. Formatting string used for outputting the underlying ReportedType.
63 //
64 // 2. Handler object for the ReportedType.  This is constructed using #1, or
65 //    else #1 is ignored completely.  This handler object is also constructed
66 //    with the output stream that will be used during formatting.
67 //
68 // --- The details of #1 and #2 together represent the ItemHandler.
69 //
70 // 3. Mode of the report.  Currently there are four modes:
71 //
72 //    a. Posting or commodity iteration.  In this mode, all the journal's
73 //       xacts, the postings of a specific xact, or all the journal's
74 //       commodities are walked.  In the first two cases, it's the underlying
75 //       postings which are passed to #2; in the second case, each
76 //       commodity is passed to #2.
77 //
78 //    b. Account iteration.  This employs step 'a', but add a prologue and
79 //       epilogue to it.  In the prologue it "sums" all account totals and
80 //       subtotals; in the epilogue it calls yet another handler whose job is
81 //       reporting (the handler used in 'a' is only for calculation).
82 //
83 //       There is one variation on 'b' in which a "totals" line is also
84 //       displayed.
85 //
86 //    c. Write journal.  In this mode, a single function is called that output
87 //       the journal object as a textual file.  #2 is used to print out each
88 //       posting in the journal.
89 //
90 //    d. Dump binary file.  This is just like 'c', except that it dumps out a
91 //       binary file and #2 is completely ignored.
92 //
93 // 4. For 'a' and 'b' in #3, there is a different iteration function called,
94 //    depending on whether we're iterating:
95 //
96 //    a. The postings of an xact: walk_postings.
97 //    b. The xacts of a journal: walk_xacts.
98 //    c. The commodities of a journal: walk_commodities.
99 //
100 // 5. Finally, for the 'a' and 'b' reporting modes, there is a variant which
101 //    says that the formatter should be "flushed" after the entities are
102 //    iterated.  This does not happen for the commodities iteration, however.
103 
104 class report_t : public scope_t
105 {
106   report_t();
107 
108 public:
109   session_t&      session;
110   output_stream_t output_stream;
111 
112 #define BUDGET_NO_BUDGET   0x00
113 #define BUDGET_BUDGETED    0x01
114 #define BUDGET_UNBUDGETED  0x02
115 #define BUDGET_WRAP_VALUES 0x04
116 
117   datetime_t    terminus;
118   uint_least8_t budget_flags;
119 
report_t(session_t & _session)120   explicit report_t(session_t& _session)
121     : session(_session), terminus(CURRENT_TIME()),
122       budget_flags(BUDGET_NO_BUDGET) {
123     TRACE_CTOR(report_t, "session_t&");
124   }
report_t(const report_t & report)125   report_t(const report_t& report)
126     : scope_t(report), session(report.session),
127       output_stream(report.output_stream),
128       terminus(report.terminus),
129       budget_flags(report.budget_flags) {
130     TRACE_CTOR(report_t, "copy");
131   }
132 
~report_t()133   virtual ~report_t() {
134     TRACE_DTOR(report_t);
135     output_stream.close();
136   }
137 
quick_close()138   void quick_close() {
139     output_stream.close();
140   }
141 
description()142   virtual string description() {
143     return _("current report");
144   }
145 
146   void normalize_options(const string& verb);
147   void normalize_period();
148   void parse_query_args(const value_t& args, const string& whence);
149 
150   void posts_report(post_handler_ptr handler);
151   void generate_report(post_handler_ptr handler);
152   void xact_report(post_handler_ptr handler, xact_t& xact);
153   void accounts_report(acct_handler_ptr handler);
154   void commodities_report(post_handler_ptr handler);
155 
156   value_t display_value(const value_t& val);
157 
158   value_t fn_amount_expr(call_scope_t& scope);
159   value_t fn_total_expr(call_scope_t& scope);
160   value_t fn_display_amount(call_scope_t& scope);
161   value_t fn_display_total(call_scope_t& scope);
162   value_t fn_top_amount(call_scope_t& val);
163   value_t fn_should_bold(call_scope_t& scope);
164   value_t fn_market(call_scope_t& scope);
165   value_t fn_get_at(call_scope_t& scope);
166   value_t fn_is_seq(call_scope_t& scope);
167   value_t fn_strip(call_scope_t& scope);
168   value_t fn_trim(call_scope_t& scope);
169   value_t fn_format(call_scope_t& scope);
170   value_t fn_print(call_scope_t& scope);
171   value_t fn_scrub(call_scope_t& scope);
172   value_t fn_quantity(call_scope_t& scope);
173   value_t fn_rounded(call_scope_t& scope);
174   value_t fn_unrounded(call_scope_t& scope);
175   value_t fn_truncated(call_scope_t& scope);
176   value_t fn_floor(call_scope_t& scope);
177   value_t fn_ceiling(call_scope_t& scope);
178   value_t fn_clear_commodity(call_scope_t& scope);
179   value_t fn_round(call_scope_t& scope);
180   value_t fn_roundto(call_scope_t& scope);
181   value_t fn_unround(call_scope_t& scope);
182   value_t fn_abs(call_scope_t& scope);
183   value_t fn_justify(call_scope_t& scope);
184   value_t fn_quoted(call_scope_t& scope);
185   value_t fn_quoted_rfc4180(call_scope_t& scope);
186   value_t fn_join(call_scope_t& scope);
187   value_t fn_format_date(call_scope_t& scope);
188   value_t fn_format_datetime(call_scope_t& scope);
189   value_t fn_ansify_if(call_scope_t& scope);
190   value_t fn_percent(call_scope_t& scope);
191   value_t fn_commodity(call_scope_t& scope);
192   value_t fn_commodity_price(call_scope_t& scope);
193   value_t fn_set_commodity_price(call_scope_t& scope);
194   value_t fn_nail_down(call_scope_t& scope);
195   value_t fn_lot_date(call_scope_t& scope);
196   value_t fn_lot_price(call_scope_t& scope);
197   value_t fn_lot_tag(call_scope_t& scope);
198   value_t fn_to_boolean(call_scope_t& scope);
199   value_t fn_to_int(call_scope_t& scope);
200   value_t fn_to_datetime(call_scope_t& scope);
201   value_t fn_to_date(call_scope_t& scope);
202   value_t fn_to_amount(call_scope_t& scope);
203   value_t fn_to_balance(call_scope_t& scope);
204   value_t fn_to_string(call_scope_t& scope);
205   value_t fn_to_mask(call_scope_t& scope);
206   value_t fn_to_sequence(call_scope_t& scope);
207   value_t fn_averaged_lots(call_scope_t& scope);
208 
fn_now(call_scope_t &)209   value_t fn_now(call_scope_t&) {
210     return terminus;
211   }
fn_today(call_scope_t &)212   value_t fn_today(call_scope_t&) {
213     return terminus.date();
214   }
215 
fn_options(call_scope_t &)216   value_t fn_options(call_scope_t&) {
217     return scope_value(this);
218   }
219 
report_format(option_t<report_t> & option)220   string report_format(option_t<report_t>& option) {
221     if (HANDLED(format_))
222       return HANDLER(format_).str();
223     return option.str();
224   }
225 
maybe_format(option_t<report_t> & option)226   optional<string> maybe_format(option_t<report_t>& option) {
227     if (option)
228       return option.str();
229     return none;
230   }
231 
232   value_t reload_command(call_scope_t&);
233   value_t echo_command(call_scope_t& scope);
234   value_t pricemap_command(call_scope_t& scope);
235 
what_to_keep()236   keep_details_t what_to_keep() {
237     bool lots = HANDLED(lots) || HANDLED(lots_actual);
238     return keep_details_t(lots || HANDLED(lot_prices),
239                           lots || HANDLED(lot_dates),
240                           lots || HANDLED(lot_notes),
241                           HANDLED(lots_actual));
242   }
243 
report_options(std::ostream & out)244   void report_options(std::ostream& out)
245   {
246     HANDLER(abbrev_len_).report(out);
247     HANDLER(account_).report(out);
248     HANDLER(actual).report(out);
249     HANDLER(add_budget).report(out);
250     HANDLER(amount_).report(out);
251     HANDLER(amount_data).report(out);
252     HANDLER(anon).report(out);
253     HANDLER(auto_match).report(out);
254     HANDLER(aux_date).report(out);
255     HANDLER(average).report(out);
256     HANDLER(balance_format_).report(out);
257     HANDLER(base).report(out);
258     HANDLER(basis).report(out);
259     HANDLER(begin_).report(out);
260     HANDLER(budget).report(out);
261     HANDLER(budget_format_).report(out);
262     HANDLER(by_payee).report(out);
263     HANDLER(cleared).report(out);
264     HANDLER(cleared_format_).report(out);
265     HANDLER(color).report(out);
266     HANDLER(collapse).report(out);
267     HANDLER(collapse_if_zero).report(out);
268     HANDLER(columns_).report(out);
269     HANDLER(csv_format_).report(out);
270     HANDLER(current).report(out);
271     HANDLER(daily).report(out);
272     HANDLER(date_).report(out);
273     HANDLER(date_format_).report(out);
274     HANDLER(datetime_format_).report(out);
275     HANDLER(dc).report(out);
276     HANDLER(depth_).report(out);
277     HANDLER(deviation).report(out);
278     HANDLER(display_).report(out);
279     HANDLER(display_amount_).report(out);
280     HANDLER(display_total_).report(out);
281     HANDLER(dow).report(out);
282     HANDLER(empty).report(out);
283     HANDLER(end_).report(out);
284     HANDLER(equity).report(out);
285     HANDLER(exact).report(out);
286     HANDLER(exchange_).report(out);
287     HANDLER(flat).report(out);
288     HANDLER(force_color).report(out);
289     HANDLER(force_pager).report(out);
290     HANDLER(forecast_while_).report(out);
291     HANDLER(forecast_years_).report(out);
292     HANDLER(format_).report(out);
293     HANDLER(gain).report(out);
294     HANDLER(generated).report(out);
295     HANDLER(group_by_).report(out);
296     HANDLER(group_title_format_).report(out);
297     HANDLER(head_).report(out);
298     HANDLER(immediate).report(out);
299     HANDLER(inject_).report(out);
300     HANDLER(invert).report(out);
301     HANDLER(limit_).report(out);
302     HANDLER(lot_dates).report(out);
303     HANDLER(lot_prices).report(out);
304     HANDLER(average_lot_prices).report(out);
305     HANDLER(lot_notes).report(out);
306     HANDLER(lots).report(out);
307     HANDLER(lots_actual).report(out);
308     HANDLER(market).report(out);
309     HANDLER(meta_).report(out);
310     HANDLER(monthly).report(out);
311     HANDLER(no_pager).report(out);
312     HANDLER(no_rounding).report(out);
313     HANDLER(no_titles).report(out);
314     HANDLER(no_total).report(out);
315     HANDLER(now_).report(out);
316     HANDLER(only_).report(out);
317     HANDLER(output_).report(out);
318     HANDLER(pager_).report(out);
319     HANDLER(payee_).report(out);
320     HANDLER(pending).report(out);
321     HANDLER(percent).report(out);
322     HANDLER(period_).report(out);
323     HANDLER(pivot_).report(out);
324     HANDLER(plot_amount_format_).report(out);
325     HANDLER(plot_total_format_).report(out);
326     HANDLER(prepend_format_).report(out);
327     HANDLER(prepend_width_).report(out);
328     HANDLER(price).report(out);
329     HANDLER(prices_format_).report(out);
330     HANDLER(pricedb_format_).report(out);
331     HANDLER(primary_date).report(out);
332     HANDLER(quantity).report(out);
333     HANDLER(quarterly).report(out);
334     HANDLER(raw).report(out);
335     HANDLER(real).report(out);
336     HANDLER(register_format_).report(out);
337     HANDLER(related).report(out);
338     HANDLER(related_all).report(out);
339     HANDLER(revalued).report(out);
340     HANDLER(revalued_only).report(out);
341     HANDLER(revalued_total_).report(out);
342     HANDLER(rich_data).report(out);
343     HANDLER(seed_).report(out);
344     HANDLER(sort_).report(out);
345     HANDLER(sort_all_).report(out);
346     HANDLER(sort_xacts_).report(out);
347     HANDLER(start_of_week_).report(out);
348     HANDLER(subtotal).report(out);
349     HANDLER(tail_).report(out);
350     HANDLER(time_report).report(out);
351     HANDLER(total_).report(out);
352     HANDLER(total_data).report(out);
353     HANDLER(truncate_).report(out);
354     HANDLER(unbudgeted).report(out);
355     HANDLER(uncleared).report(out);
356     HANDLER(unrealized).report(out);
357     HANDLER(unrealized_gains_).report(out);
358     HANDLER(unrealized_losses_).report(out);
359     HANDLER(unround).report(out);
360     HANDLER(weekly).report(out);
361     HANDLER(wide).report(out);
362     HANDLER(yearly).report(out);
363     HANDLER(meta_width_).report(out);
364     HANDLER(date_width_).report(out);
365     HANDLER(payee_width_).report(out);
366     HANDLER(account_width_).report(out);
367     HANDLER(amount_width_).report(out);
368     HANDLER(total_width_).report(out);
369     HANDLER(values).report(out);
370   }
371 
372   option_t<report_t> * lookup_option(const char * p);
373 
374   virtual void define(const symbol_t::kind_t kind, const string& name,
375                       expr_t::ptr_op_t def);
376 
377   virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind,
378                                   const string& name);
379 
380   /**
381    * Option handlers
382    */
383 
384   OPTION__
385   (report_t, abbrev_len_,
386    CTOR(report_t, abbrev_len_) {
387     on(none, "2");
388   });
389 
390   OPTION(report_t, account_);
391 
392   OPTION_(report_t, actual, DO() { // -L
393       OTHER(limit_).on(whence, "actual");
394     });
395 
396   OPTION_(report_t, add_budget, DO() {
397       parent->budget_flags |= BUDGET_BUDGETED | BUDGET_UNBUDGETED;
398     });
399 
400   OPTION__
401   (report_t, amount_, // -t
402    DECL1(report_t, amount_, merged_expr_t, expr, ("amount_expr", "amount")) {}
403    DO_(str) {
404      expr.append(str);
405    });
406 
407   OPTION(report_t, amount_data); // -j
408   OPTION(report_t, anon);
409   OPTION(report_t, auto_match);
410 
411   OPTION_(report_t, average, DO() { // -A
412       OTHER(empty).on(whence);
413       OTHER(display_total_)
414         .on(whence, "count>0?(display_total/count):0");
415     });
416 
417   OPTION__
418   (report_t, balance_format_,
419    CTOR(report_t, balance_format_) {
420     on(none,
421        "%(ansify_if("
422        "  justify(scrub(display_total), 20,"
423        "          20 + int(prepend_width), true, color),"
424        "            bold if should_bold))"
425        "  %(!options.flat ? depth_spacer : \"\")"
426        "%-(ansify_if("
427        "   ansify_if(partial_account(options.flat), blue if color),"
428        "             bold if should_bold))\n%/"
429        "%$1\n%/"
430        "%(prepend_width ? \" \" * int(prepend_width) : \"\")"
431        "--------------------\n");
432   });
433 
434   OPTION(report_t, base);
435 
436   OPTION_(report_t, basis, DO() { // -B
437       OTHER(revalued).on(whence);
438       OTHER(amount_).expr.set_base_expr("rounded(cost)");
439     });
440 
441   OPTION_(report_t, begin_, DO_(str) { // -b
442       date_interval_t interval(str);
443       if (optional<date_t> begin = interval.begin()) {
444         string predicate = "date>=[" + to_iso_extended_string(*begin) + "]";
445         OTHER(limit_).on(whence, predicate);
446       } else {
447         throw_(std::invalid_argument,
448                _f("Could not determine beginning of period '%1%'") % str);
449       }
450     });
451 
452   OPTION_
453   (report_t, bold_if_,
454    expr_t expr;
455    DO_(str) {
456      expr = str;
457    });
458 
459   OPTION_(report_t, budget, DO() {
460       parent->budget_flags |= BUDGET_BUDGETED;
461     });
462 
463   OPTION__
464   (report_t, budget_format_,
465    CTOR(report_t, budget_format_) {
466     on(none,
467        "%(justify(scrub(get_at(display_total, 0)), int(amount_width), -1, true, color))"
468        " %(justify(-scrub(get_at(display_total, 1)), int(amount_width), "
469        "           int(amount_width) + 1 + int(amount_width), true, color))"
470        " %(justify(scrub((get_at(display_total, 1) || 0) + "
471        "                 (get_at(display_total, 0) || 0)), int(amount_width), "
472        "           int(amount_width) + 1 + int(amount_width) + 1 + int(amount_width), true, color))"
473        " %(ansify_if("
474        "   justify((get_at(display_total, 1) ? "
475        "            (100% * quantity(scrub(get_at(display_total, 0)))) / "
476        "             -quantity(scrub(get_at(display_total, 1))) : 0), "
477        "           5, -1, true, false),"
478        "   magenta if (color and get_at(display_total, 1) and "
479        "               (abs(quantity(scrub(get_at(display_total, 0))) / "
480        "                    quantity(scrub(get_at(display_total, 1)))) >= 1))))"
481        "  %(!options.flat ? depth_spacer : \"\")"
482        "%-(ansify_if(partial_account(options.flat), blue if color))\n"
483        "%/%$1 %$2 %$3 %$4\n%/"
484        "%(prepend_width ? \" \" * int(prepend_width) : \"\")"
485        "------------ ------------ ------------ -----\n");
486   });
487 
488   OPTION(report_t, by_payee); // -P
489 
490   OPTION_(report_t, cleared, DO() { // -C
491       OTHER(limit_).on(whence, "cleared");
492     });
493 
494   OPTION__
495   (report_t, cleared_format_,
496    CTOR(report_t, cleared_format_) {
497     on(none,
498        "%(justify(scrub(get_at(display_total, 0)), 16, 16 + int(prepend_width), "
499        " true, color))  %(justify(scrub(get_at(display_total, 1)), 18, "
500        " 36 + int(prepend_width), true, color))"
501        "    %(latest_cleared ? format_date(latest_cleared) : \"         \")"
502        "    %(!options.flat ? depth_spacer : \"\")"
503        "%-(ansify_if(partial_account(options.flat), blue if color))\n%/"
504        "%$1  %$2    %$3\n%/"
505        "%(prepend_width ? \" \" * int(prepend_width) : \"\")"
506        "----------------    ----------------    ---------\n");
507   });
508 
509   OPTION(report_t, color);
510 
511   OPTION_(report_t, collapse, DO() { // -n
512       // Make sure that balance reports are collapsed too, but only apply it
513       // to account xacts
514       OTHER(display_).on(whence, "post|depth<=1");
515     });
516 
517   OPTION_(report_t, collapse_if_zero, DO() {
518       OTHER(collapse).on(whence);
519     });
520 
521   OPTION(report_t, columns_);
522   OPTION(report_t, count);
523 
524   OPTION__
525   (report_t, csv_format_,
526    CTOR(report_t, csv_format_) {
527     on(none,
528        "%(quoted(date)),"
529        "%(quoted(code)),"
530        "%(quoted(payee)),"
531        "%(quoted(display_account)),"
532        "%(quoted(commodity(scrub(display_amount)))),"
533        "%(quoted(quantity(scrub(display_amount)))),"
534        "%(quoted(cleared ? \"*\" : (pending ? \"!\" : \"\"))),"
535        "%(quoted(join(note | xact.note)))\n");
536   });
537 
538   OPTION_(report_t, current, DO() { // -c
539       OTHER(limit_).on(whence, "date<=today");
540     });
541 
542   OPTION_(report_t, daily, DO() { // -D
543       OTHER(period_).on(whence, "daily");
544     });
545 
546   OPTION(report_t, date_);
547   OPTION(report_t, date_format_);
548   OPTION(report_t, datetime_format_);
549 
550   OPTION_(report_t, dc, DO() {
551       OTHER(amount_).expr.set_base_expr
552         ("(amount > 0 ? amount : 0, amount < 0 ? amount : 0)");
553 
554       OTHER(register_format_)
555         .on(none,
556             "%(ansify_if("
557             "  ansify_if(justify(format_date(date), int(date_width)),"
558             "            green if color and date > today),"
559             "            bold if should_bold))"
560             " %(ansify_if("
561             "   ansify_if(justify(truncated(payee, int(payee_width)), int(payee_width)), "
562             "             bold if color and !cleared and actual),"
563             "             bold if should_bold))"
564             " %(ansify_if("
565             "   ansify_if(justify(truncated(display_account, int(account_width), "
566             "                               int(abbrev_len)), int(account_width)),"
567             "             blue if color),"
568             "             bold if should_bold))"
569             " %(ansify_if("
570             "   justify(scrub(abs(get_at(display_amount, 0))), int(amount_width), "
571             "           3 + int(meta_width) + int(date_width) + int(payee_width)"
572             "             + int(account_width) + int(amount_width) + int(prepend_width),"
573             "           true, color),"
574             "           bold if should_bold))"
575             " %(ansify_if("
576             "   justify(scrub(abs(get_at(display_amount, 1))), int(amount_width), "
577             "           4 + int(meta_width) + int(date_width) + int(payee_width)"
578             "             + int(account_width) + int(amount_width) + int(amount_width) + int(prepend_width),"
579             "           true, color),"
580             "           bold if should_bold))"
581             " %(ansify_if("
582             "   justify(scrub(get_at(display_total, 0) + get_at(display_total, 1)), int(total_width), "
583             "           5 + int(meta_width) + int(date_width) + int(payee_width)"
584             "             + int(account_width) + int(amount_width) + int(amount_width) + int(total_width)"
585             "             + int(prepend_width), true, color),"
586             "           bold if should_bold))\n%/"
587             "%(justify(\" \", int(date_width)))"
588             " %(ansify_if("
589             "   justify(truncated(has_tag(\"Payee\") ? payee : \" \", "
590             "                     int(payee_width)), int(payee_width)),"
591             "             bold if should_bold))"
592             " %$3 %$4 %$5 %$6\n");
593 
594       OTHER(balance_format_)
595         .on(none,
596             "%(ansify_if("
597             "  justify(scrub(abs(get_at(display_total, 0))), 14,"
598             "          14 + int(prepend_width), true, color),"
599             "            bold if should_bold)) "
600             "%(ansify_if("
601             "  justify(scrub(abs(get_at(display_total, 1))), 14,"
602             "          14 + 1 + int(prepend_width) + int(total_width), true, color),"
603             "            bold if should_bold)) "
604             "%(ansify_if("
605             "  justify(scrub(get_at(display_total, 0) + get_at(display_total, 1)), 14,"
606             "          14 + 2 + int(prepend_width) + int(total_width) + int(total_width), true, color),"
607             "            bold if should_bold))"
608             "  %(!options.flat ? depth_spacer : \"\")"
609             "%-(ansify_if("
610             "   ansify_if(partial_account(options.flat), blue if color),"
611             "             bold if should_bold))\n%/"
612             "%$1 %$2 %$3\n%/"
613             "%(prepend_width ? \" \" * int(prepend_width) : \"\")"
614             "--------------------------------------------\n");
615   });
616 
617   OPTION_(report_t, depth_, DO_(str) {
618       OTHER(display_).on(whence, string("depth<=") + str);
619     });
620 
621   OPTION_(report_t, deviation, DO() {
622       OTHER(display_total_)
623         .on(whence, "display_amount-display_total");
624     });
625 
626   OPTION_
627   (report_t, display_,
628    DO_(str) { // -d
629      if (handled)
630        value = string("(") + value + ")&(" + str + ")";
631    });
632 
633   OPTION__
634   (report_t, display_amount_,
635    DECL1(report_t, display_amount_, merged_expr_t, expr,
636          ("display_amount", "amount_expr")) {}
637    DO_(str) {
638      expr.append(str);
639    });
640 
641   OPTION__
642   (report_t, display_total_,
643    DECL1(report_t, display_total_, merged_expr_t, expr,
644          ("display_total", "total_expr")) {}
645    DO_(str) {
646      expr.append(str);
647    });
648 
649   OPTION(report_t, dow);
650   OPTION(report_t, aux_date);
651   OPTION(report_t, empty); // -E
652 
653   OPTION_(report_t, end_, DO_(str) { // -e
654       // Use begin() here so that if the user says --end=2008, we end on
655       // 2008/01/01 instead of 2009/01/01 (which is what end() would
656       // return).
657       date_interval_t interval(str);
658       if (optional<date_t> end = interval.begin()) {
659         string predicate = "date<[" + to_iso_extended_string(*end) + "]";
660         OTHER(limit_).on(whence, predicate);
661 
662         parent->terminus = datetime_t(*end);
663       } else {
664         throw_(std::invalid_argument,
665                _f("Could not determine end of period '%1%'")
666                % str);
667       }
668     });
669 
670   OPTION(report_t, equity);
671   OPTION(report_t, exact);
672 
673   OPTION_(report_t, exchange_, DO_(str) { // -X
674       // Using -X implies -V.  The main difference is that now
675       // HANDLER(exchange_) contains the name of a commodity, which
676       // is accessed via the "exchange" value expression function.
677       OTHER(market).on(whence);
678     });
679 
680   OPTION(report_t, flat);
681   OPTION(report_t, force_color);
682   OPTION(report_t, force_pager);
683   OPTION(report_t, forecast_while_);
684   OPTION(report_t, forecast_years_);
685   OPTION(report_t, format_); // -F
686 
687   OPTION_(report_t, gain, DO() { // -G
688       OTHER(revalued).on(whence);
689       OTHER(amount_).expr.set_base_expr("(amount, cost)");
690 
691       // Since we are displaying the amounts of revalued postings, they
692       // will end up being composite totals, and hence a pair of pairs.
693       OTHER(display_amount_)
694         .on(whence,
695             "use_direct_amount ? amount :"
696             " (is_seq(get_at(amount_expr, 0)) ?"
697             "  get_at(get_at(amount_expr, 0), 0) :"
698             "  market(get_at(amount_expr, 0), value_date, exchange)"
699             "  - get_at(amount_expr, 1))");
700       OTHER(revalued_total_)
701         .on(whence,
702             "(market(get_at(total_expr, 0), value_date, exchange), "
703             "get_at(total_expr, 1))");
704       OTHER(display_total_)
705         .on(whence,
706             "use_direct_amount ? total_expr :"
707             " market(get_at(total_expr, 0), value_date, exchange)"
708             " - get_at(total_expr, 1)");
709     });
710 
711   OPTION(report_t, generated);
712 
713   OPTION_
714   (report_t, group_by_,
715    expr_t expr;
716    DO_(str) {
717      expr = str;
718    });
719 
720   OPTION__
721   (report_t, group_title_format_,
722    CTOR(report_t, group_title_format_) {
723     on(none, "%(value)\n");
724   });
725 
726   OPTION(report_t, head_);
727 
728   OPTION_(report_t, historical, DO() { // -H
729       OTHER(market).on(whence);
730       OTHER(amount_)
731         .on(whence, "nail_down(amount_expr, "
732             "market(amount_expr, value_date, exchange))");
733     });
734 
735   OPTION(report_t, immediate);
736   OPTION(report_t, inject_);
737 
738   OPTION_(report_t, invert, DO() {
739       OTHER(amount_).on(whence, "-amount_expr");
740     });
741 
742   OPTION_
743   (report_t, limit_,
744    DO_(str) { // -l
745      if (handled)
746        value = string("(") + value + ")&(" + str + ")";
747    });
748 
749   OPTION(report_t, lot_dates);
750   OPTION(report_t, lot_prices);
751   OPTION_(report_t, average_lot_prices, DO() {
752       OTHER(lot_prices).on(whence);
753       OTHER(display_amount_)
754         .on(whence, "averaged_lots(display_amount)");
755       OTHER(display_total_)
756         .on(whence, "averaged_lots(display_total)");
757     });
758   OPTION(report_t, lot_notes);
759   OPTION(report_t, lots);
760   OPTION(report_t, lots_actual);
761 
762   OPTION_(report_t, market, DO() { // -V
763       OTHER(revalued).on(whence);
764 
765       OTHER(display_amount_)
766         .on(whence, "market(display_amount, value_date, exchange)");
767       OTHER(display_total_)
768         .on(whence, "market(display_total, value_date, exchange)");
769     });
770 
771   OPTION(report_t, meta_);
772 
773   OPTION_(report_t, monthly, DO() { // -M
774       OTHER(period_).on(whence, "monthly");
775     });
776 
777   OPTION_(report_t, no_color, DO() {
778       OTHER(color).off();
779     });
780 
781   OPTION_(report_t, no_revalued, DO() {
782       OTHER(revalued).off();
783     });
784 
785   OPTION(report_t, no_rounding);
786   OPTION(report_t, no_titles);
787   OPTION(report_t, no_total);
788 
789   OPTION_(report_t, now_, DO_(str) {
790       date_interval_t interval(str);
791       if (optional<date_t> begin = interval.begin()) {
792         ledger::epoch = parent->terminus = datetime_t(*begin);
793       } else {
794         throw_(std::invalid_argument,
795                _f("Could not determine beginning of period '%1%'")
796                % str);
797       }
798     });
799 
800   OPTION_
801   (report_t, only_,
802    DO_(str) {
803      if (handled)
804        value = string("(") + value + ")&(" + str + ")";
805    });
806 
807   OPTION(report_t, output_); // -o
808 
809 // setenv() is not available on WIN32
810 #if defined(HAVE_ISATTY) and !defined(_WIN32) and !defined(__CYGWIN__)
811   OPTION__
812   (report_t, pager_,
813    CTOR(report_t, pager_) {
814      if (! std::getenv("PAGER") && isatty(STDOUT_FILENO)) {
815        bool have_less = false;
816        if (exists(path("/opt/local/bin/less")) ||
817            exists(path("/usr/local/bin/less")) ||
818            exists(path("/usr/bin/less")))
819          have_less = true;
820 
821        if (have_less) {
822          on(none, "less");
823          setenv("LESS", "-FRSX", 0); // don't overwrite
824        }
825      }
826    });
827 #else // HAVE_ISATTY
828   OPTION(report_t, pager_);
829 #endif // HAVE_ISATTY
830 
831   OPTION_(report_t, no_pager, DO() {
832       OTHER(pager_).off();
833     });
834 
835   OPTION(report_t, payee_);
836 
837   OPTION_(report_t, pending, DO() { // -C
838       OTHER(limit_).on(whence, "pending");
839     });
840 
841   OPTION_(report_t, percent, DO() { // -%
842       OTHER(total_)
843         .on(whence,
844             "((is_account&parent&parent.total)?"
845             "  percent(scrub(total), scrub(parent.total)):0)");
846     });
847 
848   OPTION_
849   (report_t, period_,
850    DO_(str) { // -p
851      if (handled)
852        value += string(" ") + str;
853    });
854 
855   OPTION(report_t, pivot_);
856 
857   OPTION__
858   (report_t, plot_amount_format_,
859    CTOR(report_t, plot_amount_format_) {
860     on(none,
861        "%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(display_amount)))\n");
862   });
863 
864   OPTION__
865   (report_t, plot_total_format_,
866    CTOR(report_t, plot_total_format_) {
867     on(none,
868        "%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(display_total)))\n");
869   });
870 
871   OPTION(report_t, prepend_format_);
872   OPTION(report_t, prepend_width_);
873 
874   OPTION_(report_t, price, DO() { // -I
875       OTHER(amount_).expr.set_base_expr("price");
876     });
877 
878   OPTION__
879   (report_t, prices_format_,
880    CTOR(report_t, prices_format_) {
881     on(none,
882        "%(date) %-8(display_account) %(justify(scrub(display_amount), 12, "
883        "    2 + 9 + 8 + 12, true, color))\n");
884   });
885 
886   OPTION__
887   (report_t, pricedb_format_,
888    CTOR(report_t, pricedb_format_) {
889     on(none,
890        "P %(datetime) %(display_account) %(scrub(display_amount))\n");
891   });
892 
893   OPTION(report_t, primary_date);
894 
895   OPTION_(report_t, quantity, DO() { // -O
896       OTHER(revalued).off();
897 
898       OTHER(amount_).expr.set_base_expr("amount");
899       OTHER(total_).expr.set_base_expr("total");
900     });
901 
902   OPTION_(report_t, quarterly, DO() {
903       OTHER(period_).on(whence, "quarterly");
904     });
905 
906   OPTION(report_t, raw);
907 
908   OPTION_(report_t, real, DO() { // -R
909       OTHER(limit_).on(whence, "real");
910     });
911 
912   OPTION__
913   (report_t, register_format_,
914    CTOR(report_t, register_format_) {
915     on(none,
916        "%(ansify_if("
917        "  ansify_if(justify(format_date(date), int(date_width)),"
918        "            green if color and date > today),"
919        "            bold if should_bold))"
920        " %(ansify_if("
921        "   ansify_if(justify(truncated(payee, int(payee_width)), int(payee_width)), "
922        "             bold if color and !cleared and actual),"
923        "             bold if should_bold))"
924        " %(ansify_if("
925        "   ansify_if(justify(truncated(display_account, int(account_width), "
926        "                               int(abbrev_len)), int(account_width)),"
927        "             blue if color),"
928        "             bold if should_bold))"
929        " %(ansify_if("
930        "   justify(scrub(display_amount), int(amount_width), "
931        "           3 + int(meta_width) + int(date_width) + int(payee_width)"
932        "             + int(account_width) + int(amount_width) + int(prepend_width),"
933        "           true, color),"
934        "           bold if should_bold))"
935        " %(ansify_if("
936        "   justify(scrub(display_total), int(total_width), "
937        "           4 + int(meta_width) + int(date_width) + int(payee_width)"
938        "             + int(account_width) + int(amount_width) + int(total_width)"
939        "             + int(prepend_width), true, color),"
940        "           bold if should_bold))\n%/"
941        "%(justify(\" \", int(date_width)))"
942        " %(ansify_if("
943        "   justify(truncated(has_tag(\"Payee\") ? payee : \" \", "
944        "                     int(payee_width)), int(payee_width)),"
945        "             bold if should_bold))"
946        " %$3 %$4 %$5\n");
947   });
948 
949   OPTION(report_t, related); // -r
950 
951   OPTION_(report_t, related_all, DO() {
952       OTHER(related).on(whence);
953     });
954 
955   OPTION(report_t, revalued);
956   OPTION(report_t, revalued_only);
957 
958   OPTION_
959   (report_t, revalued_total_,
960    expr_t expr;
961    DO_(str) {
962      expr = str;
963    });
964 
965   OPTION(report_t, rich_data);
966 
967   OPTION(report_t, seed_);
968 
969   OPTION_(report_t, sort_, DO_(str) { // -S
970       OTHER(sort_xacts_).off();
971       OTHER(sort_all_).off();
972     });
973 
974   OPTION_(report_t, sort_all_, DO_(str) {
975       OTHER(sort_).on(whence, str);
976       OTHER(sort_xacts_).off();
977     });
978 
979   OPTION_(report_t, sort_xacts_, DO_(str) {
980       OTHER(sort_).on(whence, str);
981       OTHER(sort_all_).off();
982     });
983 
984   OPTION(report_t, start_of_week_);
985   OPTION(report_t, subtotal); // -s
986   OPTION(report_t, tail_);
987 
988   OPTION_(report_t, time_report, DO() {
989       OTHER(balance_format_)
990         .on(none,
991             "%(ansify_if(justify(earliest_checkin ? "
992             "     format_datetime(earliest_checkin) : \"\", 19, -1, true),"
993             "     bold if latest_checkout_cleared))  "
994             "%(ansify_if(justify(latest_checkout ? "
995             "     format_datetime(latest_checkout) : \"\", 19, -1, true), "
996             "     bold if latest_checkout_cleared)) "
997             "%(latest_checkout_cleared ? \"*\" : \" \")  "
998             "%(ansify_if("
999             "  justify(scrub(display_total), 8,"
1000             "          8 + 4 + 19 * 2, true, color), bold if should_bold))"
1001             "  %(!options.flat ? depth_spacer : \"\")"
1002             "%-(ansify_if("
1003             "   ansify_if(partial_account(options.flat), blue if color),"
1004             "             bold if should_bold))\n%/"
1005             "%$1  %$2  %$3\n%/"
1006             "%(prepend_width ? \" \" * int(prepend_width) : \"\")"
1007             "--------------------------------------------------\n");
1008     });
1009 
1010   OPTION__
1011   (report_t, total_, // -T
1012    DECL1(report_t, total_, merged_expr_t, expr, ("total_expr", "total")) {}
1013    DO_(str) {
1014      expr.append(str);
1015    });
1016 
1017   OPTION(report_t, total_data); // -J
1018 
1019   OPTION_(report_t, truncate_, DO_(style) {
1020       if (style == "leading")
1021         format_t::default_style = format_t::TRUNCATE_LEADING;
1022       else if (style == "middle")
1023         format_t::default_style = format_t::TRUNCATE_MIDDLE;
1024       else if (style == "trailing")
1025         format_t::default_style = format_t::TRUNCATE_TRAILING;
1026       else
1027         throw_(std::invalid_argument,
1028                _f("Unrecognized truncation style: '%1%'") % style);
1029       format_t::default_style_changed = true;
1030     });
1031 
1032   OPTION_(report_t, unbudgeted, DO() {
1033       parent->budget_flags |= BUDGET_UNBUDGETED;
1034     });
1035 
1036   OPTION_(report_t, uncleared, DO() { // -U
1037       OTHER(limit_).on(whence, "uncleared|pending");
1038     });
1039 
1040   OPTION(report_t, unrealized);
1041 
1042   OPTION(report_t, unrealized_gains_);
1043   OPTION(report_t, unrealized_losses_);
1044 
1045   OPTION_(report_t, unround, DO() {
1046       OTHER(amount_).on(whence, "unrounded(amount_expr)");
1047       OTHER(total_).on(whence, "unrounded(total_expr)");
1048     });
1049 
1050   OPTION_(report_t, weekly, DO() { // -W
1051       OTHER(period_).on(whence, "weekly");
1052     });
1053 
1054   OPTION_(report_t, wide, DO() { // -w
1055       OTHER(columns_).on(whence, "132");
1056     });
1057 
1058   OPTION_(report_t, yearly, DO() { // -Y
1059       OTHER(period_).on(whence, "yearly");
1060     });
1061 
1062   OPTION(report_t, meta_width_);
1063   OPTION(report_t, date_width_);
1064   OPTION(report_t, payee_width_);
1065   OPTION(report_t, account_width_);
1066   OPTION(report_t, amount_width_);
1067   OPTION(report_t, total_width_);
1068   OPTION(report_t, values);
1069 };
1070 
1071 template <class Type        = post_t,
1072           class handler_ptr = post_handler_ptr,
1073           void (report_t::*report_method)(handler_ptr) =
1074             &report_t::posts_report>
1075 class reporter
1076 {
1077   shared_ptr<item_handler<Type> > handler;
1078 
1079   report_t& report;
1080   string    whence;
1081 
1082 public:
reporter(shared_ptr<item_handler<Type>> _handler,report_t & _report,const string & _whence)1083   reporter(shared_ptr<item_handler<Type> > _handler,
1084            report_t& _report, const string& _whence)
1085     : handler(_handler), report(_report), whence(_whence) {
1086     TRACE_CTOR(reporter, "item_handler<Type>, report_t&, string");
1087   }
reporter(const reporter & other)1088   reporter(const reporter& other)
1089     : handler(other.handler), report(other.report), whence(other.whence) {
1090     TRACE_CTOR(reporter, "copy");
1091   }
throw()1092   ~reporter() throw() {
1093     TRACE_DTOR(reporter);
1094   }
1095 
operator()1096   value_t operator()(call_scope_t& args)
1097   {
1098     if (args.size() > 0)
1099       report.parse_query_args(args.value(), whence);
1100 
1101     (report.*report_method)(handler_ptr(handler));
1102 
1103     return true;
1104   }
1105 };
1106 
1107 } // namespace ledger
1108 
1109 #endif // _REPORT_H
1110