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