GnuCash c935c2f+
Loading...
Searching...
No Matches
gnucash-commands.cpp
1/*
2 * gnucash-cli.cpp -- The command line entry point for GnuCash
3 *
4 * Copyright (C) 2020 Geert Janssens <geert@kobaltwit.be>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of
9 * the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, contact:
18 *
19 * Free Software Foundation Voice: +1-617-542-5942
20 * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
21 * Boston, MA 02110-1301, USA gnu@gnu.org
22 */
23#include <config.h>
24
25#include <libguile.h>
26#include <guile-mappings.h>
27#ifdef __MINGW32__
28#include <Windows.h>
29#include <fcntl.h>
30#endif
31
32#include "gnucash-commands.hpp"
33#include "gnucash-core-app.hpp"
34
35#include <gnc-filepath-utils.h>
36#include <gnc-engine-guile.h>
37#include <gnc-prefs.h>
38#include <gnc-prefs-utils.h>
39#include <gnc-session.h>
40#include <qoflog.h>
41
42#include <boost/locale.hpp>
43#include <fstream>
44#include <iostream>
45#include <iomanip>
46#include <gnc-report.h>
47#include <gnc-quotes.hpp>
48
49namespace bl = boost::locale;
50
51static std::string empty_string{};
52
53/* This static indicates the debugging module that this .o belongs to. */
54static QofLogModule log_module = GNC_MOD_GUI;
55
56static int
57cleanup_and_exit_with_failure (QofSession *session)
58{
59 if (session)
60 {
61 auto error{qof_session_get_error (session)};
62 if (error != ERR_BACKEND_NO_ERR)
63 {
64 if (error == ERR_BACKEND_LOCKED)
65 PERR ("File is locked, won't open.");
66 else
67 PERR ("Session Error: %s\n",
68 qof_session_get_error_message (session));
69 }
70 qof_session_destroy (session);
71 }
73 return 1;
74}
75
76static void gnc_shutdown_cli (int exit_status)
77{
78 gnc_hook_run (HOOK_SHUTDOWN, NULL);
80 exit (exit_status);
81}
82
83/* scm_boot_guile doesn't expect to return, so call shutdown ourselves here */
84static void
85scm_cleanup_and_exit_with_failure (QofSession *session)
86{
87 cleanup_and_exit_with_failure (session);
88 gnc_shutdown_cli (1);
89}
90
91static void
92report_session_percentage (const char *message, double percent)
93{
94 static double previous = 0.0;
95 if ((percent - previous) < 5.0)
96 return;
97 PINFO ("\r%3.0f%% complete...", percent);
98 previous = percent;
99 return;
100}
101
102/* Don't try to use std::string& for the members of the following struct, it
103 * results in the values getting corrupted as it passes through initializing
104 * Scheme when compiled with Clang.
105 */
107 const std::string& file_to_load;
108 const std::string& run_report;
109 const std::string& export_type;
110 const std::string& output_file;
111};
112
113static inline void
114write_report_file (const char *html, const char* file)
115{
116 if (!file || !html || !*html) return;
117 auto ofs{gnc_open_filestream(file)};
118 if (!ofs)
119 {
120 std::cerr << "Failed to open file " << file << " for writing\n";
121 return;
122 }
123 ofs << html << std::endl;
124 // ofs destructor will close the file
125}
126
127static void
128scm_run_report (void *data,
129 [[maybe_unused]] int argc, [[maybe_unused]] char **argv)
130{
131 auto args = static_cast<run_report_args*>(data);
132
133 scm_c_eval_string("(debug-set! stack 200000)");
134 scm_c_use_module ("gnucash utilities");
135 scm_c_use_module ("gnucash app-utils");
136 scm_c_use_module ("gnucash reports");
137
138 gnc_report_init ();
139 Gnucash::gnc_load_scm_config ([](const gchar *msg){ PINFO ("%s", msg); });
142
143 auto datafile = args->file_to_load.c_str();
144 auto check_report_cmd = scm_c_eval_string ("gnc:cmdline-check-report");
145 auto get_report_cmd = scm_c_eval_string ("gnc:cmdline-get-report-id");
146 auto run_export_cmd = scm_c_eval_string ("gnc:cmdline-template-export");
147 /* We generally insist on using scm_from_utf8_string() throughout GnuCash
148 * because all GUI-sourced strings and all file-sourced strings are encoded
149 * that way. In this case, though, the input is coming from a shell window
150 * and Microsoft Windows shells are generally not capable of entering UTF8
151 * so it's necessary here to allow guile to read the locale and interpret
152 * the input in that encoding.
153 */
154 auto report = scm_from_locale_string (args->run_report.c_str());
155 auto type = !args->export_type.empty() ?
156 scm_from_locale_string (args->export_type.c_str()) : SCM_BOOL_F;
157
158 if (scm_is_false (scm_call_2 (check_report_cmd, report, type)))
159 scm_cleanup_and_exit_with_failure (nullptr);
160
161 PINFO ("Loading datafile %s...\n", datafile);
162
163 auto session = gnc_get_current_session ();
164 if (!session)
165 scm_cleanup_and_exit_with_failure (session);
166
167 qof_session_begin (session, datafile, SESSION_READ_ONLY);
168 if (qof_session_get_error (session) != ERR_BACKEND_NO_ERR)
169 scm_cleanup_and_exit_with_failure (session);
170
171 qof_session_load (session, report_session_percentage);
172 if (qof_session_get_error (session) != ERR_BACKEND_NO_ERR)
173 scm_cleanup_and_exit_with_failure (session);
174
175 if (!args->export_type.empty())
176 {
177 SCM retval = scm_call_2 (run_export_cmd, report, type);
178 SCM query_result = scm_c_eval_string ("gnc:html-document?");
179 SCM get_export_string = scm_c_eval_string ("gnc:html-document-export-string");
180 SCM get_export_error = scm_c_eval_string ("gnc:html-document-export-error");
181
182 if (scm_is_false (scm_call_1 (query_result, retval)))
183 {
184 std::cerr << _("This report must be upgraded to \
185return a document object with export-string or export-error.") << std::endl;
186 scm_cleanup_and_exit_with_failure (nullptr);
187 }
188
189 SCM export_string = scm_call_1 (get_export_string, retval);
190 SCM export_error = scm_call_1 (get_export_error, retval);
191
192 if (scm_is_string (export_string))
193 {
194 auto output = scm_to_utf8_string (export_string);
195 if (!args->output_file.empty())
196 {
197 write_report_file(output, args->output_file.c_str());
198 }
199 else
200 {
201 std::cout << output << std::endl;
202 }
203 g_free (output);
204 }
205 else if (scm_is_string (export_error))
206 {
207 auto err = scm_to_utf8_string (export_error);
208 std::cerr << err << std::endl;
209 g_free (err);
210 scm_cleanup_and_exit_with_failure (nullptr);
211 }
212 else
213 {
214 std::cerr << _("This report must be upgraded to \
215return a document object with export-string or export-error.") << std::endl;
216 scm_cleanup_and_exit_with_failure (nullptr);
217 }
218 }
219 else
220 {
221 SCM id = scm_call_1(get_report_cmd, report);
222
223 if (scm_is_false (id))
224 scm_cleanup_and_exit_with_failure (nullptr);
225 char *html, *errmsg;
226
227 if (gnc_run_report_with_error_handling (scm_to_int(id), &html, &errmsg))
228 {
229 if (!args->output_file.empty())
230 {
231 write_report_file(html, args->output_file.c_str());
232 }
233 else
234 {
235 std::cout << html << std::endl;
236 }
237 g_free (html);
238 }
239 else
240 {
241 std::cerr << errmsg << std::endl;
242 g_free (errmsg);
243 }
244 }
245
246 qof_session_destroy (session);
247
249 gnc_shutdown_cli (0);
250 return;
251}
252
253
255 const std::string& file_to_load;
256 const std::string& show_report;
257};
258
259static void
260scm_report_show (void *data,
261 [[maybe_unused]] int argc, [[maybe_unused]] char **argv)
262{
263 auto args = static_cast<show_report_args*>(data);
264
265 scm_c_eval_string("(debug-set! stack 200000)");
266 scm_c_use_module ("gnucash utilities");
267 scm_c_use_module ("gnucash app-utils");
268 scm_c_use_module ("gnucash reports");
269 gnc_report_init ();
270 Gnucash::gnc_load_scm_config ([](const gchar *msg){ PINFO ("%s", msg); });
271
272 if (!args->file_to_load.empty())
273 {
274 auto datafile = args->file_to_load.c_str();
275 PINFO ("Loading datafile %s...\n", datafile);
276
277 auto session = gnc_get_current_session ();
278 if (session)
279 {
280 qof_session_begin (session, datafile, SESSION_READ_ONLY);
281 if (qof_session_get_error (session) == ERR_BACKEND_NO_ERR)
282 qof_session_load (session, report_session_percentage);
283 }
284 }
285
286 scm_call_2 (scm_c_eval_string ("gnc:cmdline-report-show"),
287 scm_from_locale_string (args->show_report.c_str ()),
288 scm_current_output_port ());
289 gnc_shutdown_cli (0);
290 return;
291}
292
293
294static void
295scm_report_list ([[maybe_unused]] void *data,
296 [[maybe_unused]] int argc, [[maybe_unused]] char **argv)
297{
298 scm_c_eval_string("(debug-set! stack 200000)");
299 scm_c_use_module ("gnucash app-utils");
300 scm_c_use_module ("gnucash reports");
301 gnc_report_init ();
302 Gnucash::gnc_load_scm_config ([](const gchar *msg){ PINFO ("%s", msg); });
303
304 scm_call_1 (scm_c_eval_string ("gnc:cmdline-report-list"),
305 scm_current_output_port ());
306 gnc_shutdown_cli (0);
307 return;
308}
309
310int
311Gnucash::check_finance_quote (void)
312{
314 try
315 {
316 GncQuotes quotes;
317 std::cout << bl::format (bl::translate ("Found Finance::Quote version {1}.")) % quotes.version() << "\n";
318 std::cout << bl::translate ("Finance::Quote sources:\n");
319 int count{0};
320 const auto width{12};
321 for (auto source : quotes.sources())
322 {
323 auto mul{source.length() / width + 1};
324 count += mul;
325 if (count > 6)
326 {
327 count = mul;
328 std::cout << "\n";
329 }
330 std::cout << std::setw(mul * (width + 1)) << std::left << source;
331 }
332 std::cout << std::endl;
333 return 0;
334 }
335 catch (const GncQuoteException& err)
336 {
337 std::cout << err.what() << std::endl;
338 return 1;
339 }
340}
341
342int
343Gnucash::add_quotes (const bo_str& uri)
344{
345 int rv{};
348
349 auto session = gnc_get_current_session();
350 if (!session)
351 return 1;
352
353 qof_session_begin(session, uri->c_str(), SESSION_NORMAL_OPEN);
354 if (qof_session_get_error(session) != ERR_BACKEND_NO_ERR)
355 return cleanup_and_exit_with_failure (session);
356
357 qof_session_load(session, NULL);
358 if (qof_session_get_error(session) != ERR_BACKEND_NO_ERR)
359 return cleanup_and_exit_with_failure (session);
360
361 try
362 {
363 GncQuotes quotes;
364 std::cout << bl::format (bl::translate ("Found Finance::Quote version {1}.")) % quotes.version() << std::endl;
365 auto quote_sources = quotes.sources();
366 gnc_quote_source_set_fq_installed (quotes.version().c_str(), quote_sources);
367 quotes.fetch(qof_session_get_book(session));
368 if (quotes.had_failures())
369 {
370 std::cerr << quotes.report_failures() << std::endl;
371 rv = 2;
372 }
373 }
374 catch (const GncQuoteException& err)
375 {
376 std::cerr << bl::translate("Price retrieval failed: ") << err.what() << std::endl;
377 }
378 qof_session_save(session, NULL);
379 if (qof_session_get_error(session) != ERR_BACKEND_NO_ERR)
380 return cleanup_and_exit_with_failure (session);
381
382 qof_session_destroy(session);
384 return rv;
385}
386
387int
388Gnucash::report_quotes (const char* source, const StrVec& commodities, bool verbose)
389{
391 try
392 {
393 GncQuotes quotes;
394 quotes.report(source, commodities, verbose);
395 if (quotes.had_failures())
396 std::cerr << quotes.report_failures() << std::endl;
397 }
398 catch (const GncQuoteException& err)
399 {
400 std::cerr << bl::translate("Price retrieval failed: ") << err.what() << std::endl;
401 return -1;
402 }
403 return 0;
404}
405
406int
407Gnucash::run_report (const bo_str& file_to_load,
408 const bo_str& run_report,
409 const bo_str& export_type,
410 const bo_str& output_file)
411{
412 auto args = run_report_args { file_to_load ? *file_to_load : empty_string,
413 run_report ? *run_report : empty_string,
414 export_type ? *export_type : empty_string,
415 output_file ? *output_file : empty_string };
416 if (run_report && !run_report->empty())
417 scm_boot_guile (0, nullptr, scm_run_report, &args);
418
419 return 0;
420}
421
422int
423Gnucash::report_show (const bo_str& file_to_load,
424 const bo_str& show_report)
425{
426 auto args = show_report_args { file_to_load ? *file_to_load : empty_string,
427 show_report ? *show_report : empty_string };
428 if (show_report && !show_report->empty())
429 scm_boot_guile (0, nullptr, scm_report_show, &args);
430
431 return 0;
432}
433
434int
435Gnucash::report_list (void)
436{
437 scm_boot_guile (0, nullptr, scm_report_list, NULL);
438 return 0;
439}
bool had_failures() noexcept
Report if there were quotes requested but not retrieved.
void report(const char *source, const StrVec &commodities, bool verbose=false)
Report quote results from Finance::Quote to std::cout.
void fetch(QofBook *book)
Fetch quotes for all commodities in our db that have a quote source set.
const std::string & version() noexcept
Get the installed Finance::Quote version.
const QuoteSources & sources() noexcept
Get the available Finance::Quote sources as a std::vector.
File path resolution utility functions.
Preferences initialization function.
Generic api to store and retrieve preferences.
void qof_session_save(QofSession *session, QofPercentageFunc percentage_func)
The qof_session_save() method will commit all changes that have been made to the session.
QofBackendError qof_session_get_error(QofSession *session)
The qof_session_get_error() routine can be used to obtain the reason for any failure.
void qof_session_begin(QofSession *session, const char *uri, SessionOpenMode mode)
Begins a new session.
QofBook * qof_session_get_book(const QofSession *session)
Returns the QofBook of this session.
@ SESSION_READ_ONLY
Create a new store at the URI even if a store already exists there.
Definition qofsession.h:128
void gnc_quote_source_set_fq_installed(const char *version_string, const std::vector< std::string > &sources_list)
Update gnucash internal tables based on what Finance::Quote sources are installed.
void gnc_engine_shutdown(void)
Called to shutdown the engine.
void qof_event_resume(void)
Resume engine event generation.
Definition qofevent.cpp:156
void qof_event_suspend(void)
Suspend all engine events.
Definition qofevent.cpp:145
#define PINFO(format, args...)
Print an informational note.
Definition qoflog.h:256
#define PERR(format, args...)
Log a serious error.
Definition qoflog.h:244
void gnc_prefs_init(void)
This function is called early in the load process to preload a number of preferences from the setting...