1 /*
2  * Copyright (c) 2003-2019, 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 #include <system.hh>
33 
34 #include "output.h"
35 #include "xact.h"
36 #include "post.h"
37 #include "item.h"
38 #include "account.h"
39 #include "session.h"
40 #include "report.h"
41 
42 namespace ledger {
43 
format_posts(report_t & _report,const string & format,const optional<string> & _prepend_format,std::size_t _prepend_width)44 format_posts::format_posts(report_t&               _report,
45                            const string&           format,
46                            const optional<string>& _prepend_format,
47                            std::size_t             _prepend_width)
48   : report(_report), prepend_width(_prepend_width),
49     last_xact(NULL), last_post(NULL), first_report_title(true)
50 {
51   const char * f = format.c_str();
52 
53   if (const char * p = std::strstr(f, "%/")) {
54     first_line_format.parse_format
55       (string(f, 0, static_cast<std::string::size_type>(p - f)));
56     const char * n = p + 2;
57     if (const char * pp = std::strstr(n, "%/")) {
58       next_lines_format.parse_format
59         (string(n, 0, static_cast<std::string::size_type>(pp - n)),
60          first_line_format);
61       between_format.parse_format(string(pp + 2), first_line_format);
62     } else {
63       next_lines_format.parse_format(string(n), first_line_format);
64     }
65   } else {
66     first_line_format.parse_format(format);
67     next_lines_format.parse_format(format);
68   }
69 
70   if (_prepend_format)
71     prepend_format.parse_format(*_prepend_format);
72 
73   TRACE_CTOR(format_posts, "report&, const string&, bool");
74 }
75 
flush()76 void format_posts::flush()
77 {
78   report.output_stream.flush();
79 }
80 
operator ()(post_t & post)81 void format_posts::operator()(post_t& post)
82 {
83   if (! post.has_xdata() ||
84       ! post.xdata().has_flags(POST_EXT_DISPLAYED)) {
85     std::ostream& out(report.output_stream);
86 
87     bind_scope_t bound_scope(report, post);
88 
89     if (! report_title.empty()) {
90       if (first_report_title)
91         first_report_title = false;
92       else
93         out << '\n';
94 
95       value_scope_t val_scope(bound_scope, string_value(report_title));
96       format_t group_title_format(report.HANDLER(group_title_format_).str());
97 
98       out << group_title_format(val_scope);
99 
100       report_title = "";
101     }
102 
103     if (prepend_format) {
104       out.width(static_cast<std::streamsize>(prepend_width));
105       out << prepend_format(bound_scope);
106     }
107 
108     if (last_xact != post.xact) {
109       if (last_xact) {
110         bind_scope_t xact_scope(report, *last_xact);
111         out << between_format(xact_scope);
112       }
113       out << first_line_format(bound_scope);
114       last_xact = post.xact;
115     }
116     else if (last_post && last_post->date() != post.date()) {
117       out << first_line_format(bound_scope);
118     }
119     else {
120       out << next_lines_format(bound_scope);
121     }
122 
123     post.xdata().add_flags(POST_EXT_DISPLAYED);
124     last_post = &post;
125   }
126 }
127 
format_accounts(report_t & _report,const string & format,const optional<string> & _prepend_format,std::size_t _prepend_width)128 format_accounts::format_accounts(report_t&               _report,
129                                  const string&           format,
130                                  const optional<string>& _prepend_format,
131                                  std::size_t             _prepend_width)
132   : report(_report), prepend_width(_prepend_width), disp_pred(),
133     first_report_title(true)
134 {
135   const char * f = format.c_str();
136 
137   if (const char * p = std::strstr(f, "%/")) {
138     account_line_format.parse_format
139       (string(f, 0, static_cast<std::string::size_type>(p - f)));
140     const char * n = p + 2;
141     if (const char * pp = std::strstr(n, "%/")) {
142       total_line_format.parse_format
143         (string(n, 0, static_cast<std::string::size_type>(pp - n)),
144          account_line_format);
145       separator_format.parse_format(string(pp + 2), account_line_format);
146     } else {
147       total_line_format.parse_format(n, account_line_format);
148     }
149   } else {
150     account_line_format.parse_format(format);
151     total_line_format.parse_format(format, account_line_format);
152   }
153 
154   if (_prepend_format)
155     prepend_format.parse_format(*_prepend_format);
156 
157   TRACE_CTOR(format_accounts, "report&, const string&");
158 }
159 
post_account(account_t & account,const bool flat)160 std::size_t format_accounts::post_account(account_t& account, const bool flat)
161 {
162   if (! flat && account.parent)
163     post_account(*account.parent, flat);
164 
165   if (account.xdata().has_flags(ACCOUNT_EXT_TO_DISPLAY) &&
166       ! account.xdata().has_flags(ACCOUNT_EXT_DISPLAYED)) {
167     std::ostream& out(report.output_stream);
168 
169     DEBUG("account.display", "Displaying account: " << account.fullname());
170     account.xdata().add_flags(ACCOUNT_EXT_DISPLAYED);
171 
172     bind_scope_t bound_scope(report, account);
173 
174     if (! report_title.empty()) {
175       if (first_report_title)
176         first_report_title = false;
177       else
178         out << '\n';
179 
180       value_scope_t val_scope(bound_scope, string_value(report_title));
181       format_t group_title_format(report.HANDLER(group_title_format_).str());
182 
183       out << group_title_format(val_scope);
184 
185       report_title = "";
186     }
187 
188     if (prepend_format) {
189       out.width(static_cast<std::streamsize>(prepend_width));
190       out << prepend_format(bound_scope);
191     }
192 
193     out << account_line_format(bound_scope);
194 
195     return 1;
196   }
197   return 0;
198 }
199 
200 std::pair<std::size_t, std::size_t>
mark_accounts(account_t & account,const bool flat)201 format_accounts::mark_accounts(account_t& account, const bool flat)
202 {
203   std::size_t visited    = 0;
204   std::size_t to_display = 0;
205 
206   foreach (accounts_map::value_type& pair, account.accounts) {
207     std::pair<std::size_t, std::size_t> i = mark_accounts(*pair.second, flat);
208     visited    += i.first;
209     to_display += i.second;
210   }
211 
212 #if DEBUG_ON
213   DEBUG("account.display", "Considering account: " << account.fullname());
214   if (account.has_xflags(ACCOUNT_EXT_VISITED))
215     DEBUG("account.display", "  it was visited itself");
216   DEBUG("account.display", "  it has " << visited << " visited children");
217   DEBUG("account.display",
218         "  it has " << to_display << " children to display");
219 #endif
220 
221   if (account.parent &&
222       (account.has_xflags(ACCOUNT_EXT_VISITED) || (! flat && visited > 0))) {
223     bind_scope_t bound_scope(report, account);
224     call_scope_t call_scope(bound_scope);
225     if ((! flat && to_display > 1) ||
226         ((flat || to_display != 1 ||
227           account.has_xflags(ACCOUNT_EXT_VISITED)) &&
228          (report.HANDLED(empty) ||
229           report.display_value(report.fn_display_total(call_scope))) &&
230          disp_pred(bound_scope))) {
231       account.xdata().add_flags(ACCOUNT_EXT_TO_DISPLAY);
232       DEBUG("account.display", "Marking account as TO_DISPLAY");
233       to_display = 1;
234     }
235     visited = 1;
236   }
237 
238   return std::pair<std::size_t, std::size_t>(visited, to_display);
239 }
240 
flush()241 void format_accounts::flush()
242 {
243   std::ostream& out(report.output_stream);
244 
245   if (report.HANDLED(display_)) {
246     DEBUG("account.display",
247           "Account display predicate: " << report.HANDLER(display_).str());
248     disp_pred.parse(report.HANDLER(display_).str());
249   }
250 
251   mark_accounts(*report.session.journal->master, report.HANDLED(flat));
252 
253   std::size_t displayed = 0;
254 
255   foreach (account_t * account, posted_accounts)
256     displayed += post_account(*account, report.HANDLED(flat));
257 
258   if (displayed > 1 &&
259       ! report.HANDLED(no_total) && ! report.HANDLED(percent)) {
260     bind_scope_t bound_scope(report, *report.session.journal->master);
261     out << separator_format(bound_scope);
262 
263     if (prepend_format) {
264       static_cast<std::ostream&>(report.output_stream)
265         .width(static_cast<std::streamsize>(prepend_width));
266       static_cast<std::ostream&>(report.output_stream)
267         << prepend_format(bound_scope);
268     }
269 
270     out << total_line_format(bound_scope);
271   }
272 
273   out.flush();
274 }
275 
operator ()(account_t & account)276 void format_accounts::operator()(account_t& account)
277 {
278   DEBUG("account.display", "Posting account: " << account.fullname());
279   posted_accounts.push_back(&account);
280 }
281 
flush()282 void report_accounts::flush()
283 {
284   std::ostream& out(report.output_stream);
285   format_t      prepend_format;
286   std::size_t   prepend_width;
287   bool          do_prepend_format;
288 
289   if ((do_prepend_format = report.HANDLED(prepend_format_))) {
290     prepend_format.parse_format(report.HANDLER(prepend_format_).str());
291     prepend_width = report.HANDLED(prepend_width_)
292       ? lexical_cast<std::size_t>(report.HANDLER(prepend_width_).str())
293       : 0;
294   }
295 
296   foreach (accounts_pair& entry, accounts) {
297     if (do_prepend_format) {
298       bind_scope_t bound_scope(report, *entry.first);
299       out.width(static_cast<std::streamsize>(prepend_width));
300       out << prepend_format(bound_scope);
301     }
302 
303     if (report.HANDLED(count))
304       out << entry.second << ' ';
305     out << *entry.first << '\n';
306   }
307 }
308 
operator ()(post_t & post)309 void report_accounts::operator()(post_t& post)
310 {
311   accounts_report_map::iterator i = accounts.find(post.account);
312   if (i == accounts.end())
313     accounts.insert(accounts_pair(post.account, 1));
314   else
315     (*i).second++;
316 }
317 
flush()318 void report_payees::flush()
319 {
320   std::ostream& out(report.output_stream);
321 
322   foreach (payees_pair& entry, payees) {
323     if (report.HANDLED(count))
324       out << entry.second << ' ';
325     out << entry.first << '\n';
326   }
327 }
328 
operator ()(post_t & post)329 void report_payees::operator()(post_t& post)
330 {
331   std::map<string, std::size_t>::iterator i = payees.find(post.payee());
332   if (i == payees.end())
333     payees.insert(payees_pair(post.payee(), 1));
334   else
335     (*i).second++;
336 }
337 
flush()338 void report_tags::flush()
339 {
340   std::ostream& out(report.output_stream);
341 
342   foreach (tags_pair& entry, tags) {
343     if (report.HANDLED(count))
344       out << entry.second << ' ';
345     out << entry.first << '\n';
346   }
347 }
348 
gather_metadata(item_t & item)349 void report_tags::gather_metadata(item_t& item)
350 {
351   if (! item.metadata)
352     return;
353   foreach (const item_t::string_map::value_type& data, *item.metadata) {
354     string tag(data.first);
355     if (report.HANDLED(values) && data.second.first)
356       tag += ": " + data.second.first.get().to_string();
357 
358     std::map<string, std::size_t>::iterator i = tags.find(tag);
359     if (i == tags.end())
360       tags.insert(tags_pair(tag, 1));
361     else
362       (*i).second++;
363   }
364 }
365 
operator ()(post_t & post)366 void report_tags::operator()(post_t& post)
367 {
368   gather_metadata(*post.xact);
369   gather_metadata(post);
370 }
371 
flush()372 void report_commodities::flush()
373 {
374   std::ostream& out(report.output_stream);
375 
376   foreach (commodities_pair& entry, commodities) {
377     if (report.HANDLED(count))
378       out << entry.second << ' ';
379     out << *entry.first << '\n';
380   }
381 }
382 
operator ()(post_t & post)383 void report_commodities::operator()(post_t& post)
384 {
385   amount_t temp(post.amount.strip_annotations(report.what_to_keep()));
386   commodity_t& comm(temp.commodity());
387 
388   commodities_report_map::iterator i = commodities.find(&comm);
389   if (i == commodities.end())
390     commodities.insert(commodities_pair(&comm, 1));
391   else
392     (*i).second++;
393 
394   if (comm.has_annotation()) {
395     annotated_commodity_t& ann_comm(as_annotated_commodity(comm));
396     if (ann_comm.details.price) {
397       commodities_report_map::iterator ii =
398         commodities.find(&ann_comm.details.price->commodity());
399       if (ii == commodities.end())
400         commodities.insert
401           (commodities_pair(&ann_comm.details.price->commodity(), 1));
402       else
403         (*ii).second++;
404     }
405   }
406 
407   if (post.cost) {
408     amount_t temp_cost(post.cost->strip_annotations(report.what_to_keep()));
409     i = commodities.find(&temp_cost.commodity());
410     if (i == commodities.end())
411       commodities.insert(commodities_pair(&temp_cost.commodity(), 1));
412     else
413       (*i).second++;
414   }
415 }
416 
417 } // namespace ledger
418