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