Difference between revisions of "Custom Reports Using Eguile"
ChrisDennis (talk | contribs) (→Features) |
(→Custom for-loop syntax: fix broken <) |
||
(35 intermediate revisions by 10 users not shown) | |||
Line 8: | Line 8: | ||
For example, | For example, | ||
− | < | + | <SyntaxHighlight lang="HTML"> |
<h3><?scm:d coyname ?></h3> | <h3><?scm:d coyname ?></h3> | ||
<h2><?scm:d reportname ?> as at <?scm:d (gnc-print-date opt-date-tp) ?></h2> | <h2><?scm:d reportname ?> as at <?scm:d (gnc-print-date opt-date-tp) ?></h2> | ||
− | </ | + | </SyntaxHighlight> |
would become | would become | ||
− | < | + | <SyntaxHighlight lang="Scheme"> |
(display "<h3>")(display "Acme Tools Ltd.")(display "</h3> | (display "<h3>")(display "Acme Tools Ltd.")(display "</h3> | ||
<h2>)(display "Balance Sheet")(display " as at ") | <h2>)(display "Balance Sheet")(display " as at ") | ||
(display "05/04/2009")(display "<h2">) | (display "05/04/2009")(display "<h2">) | ||
− | </ | + | </SyntaxHighlight> |
which is then evaluated as guile code to create the text of the report. | which is then evaluated as guile code to create the text of the report. | ||
Line 25: | Line 25: | ||
<tt>/usr/share/gnucash/guile-modules/gnucash/report/eguile-gnc.scm</tt>. In general, it should go into the same folder as files such as fancy-invoice.scm. | <tt>/usr/share/gnucash/guile-modules/gnucash/report/eguile-gnc.scm</tt>. In general, it should go into the same folder as files such as fancy-invoice.scm. | ||
− | Note: GnuCash 2.3 | + | ;Note: GnuCash >=2.3 already includes eguile-gnc.scm. |
− | Install the report and its associated files in the usual way (see | + | Install the report and its associated files in the usual way (see [[Custom Reports#Loading the Report]] for a full explanation). |
− | [[Custom Reports]] | + | |
− | + | Your report should start with a define-module statement like <SyntaxHighlight lang="Scheme" inline> | |
+ | (define-module (gnucash report my-report-name)) | ||
+ | </SyntaxHighlight> and have a unique name (GnuCash 2.2.x and before)/a unique id (GnuCash 2.3.x and later). | ||
+ | |||
+ | The easiest way is to put all the files for a report into your <tt>.gnucash</tt> folder, and then add a line to <tt>.gnucash/config.user</tt> like this:<SyntaxHighlight lang="Scheme"> | ||
+ | (load "/path/to/my/.gnucash/report.scm") | ||
+ | </SyntaxHighlight> | ||
If <tt>.gnucash/config.user</tt> does not already exist, just create it and add the new line. | If <tt>.gnucash/config.user</tt> does not already exist, just create it and add the new line. | ||
For example, on a Linux system, for a user called 'chris', the Tax Invoice report can be installed by: | For example, on a Linux system, for a user called 'chris', the Tax Invoice report can be installed by: | ||
# Downloading taxinvoice.scm and taxinvoice.eguile.scm, and putting them into the <tt>/home/chris/.gnucash/</tt> folder. | # Downloading taxinvoice.scm and taxinvoice.eguile.scm, and putting them into the <tt>/home/chris/.gnucash/</tt> folder. | ||
− | # Adding this line to <tt>/home/chris/.gnucash/config.user</tt>: | + | # Adding this line to <tt>/home/chris/.gnucash/config.user</tt>:<SyntaxHighlight lang="Scheme"> |
− | + | (load "/home/chris/.gnucash/taxinvoice.scm") | |
+ | </SyntaxHighlight> | ||
Restart GnuCash, and the report should then show up somewhere in the reports menu, depending on the menu-path option in the report. The Tax Invoice and Balance Sheet reports (see below) put themselves into the Reports/Business sub-menu. | Restart GnuCash, and the report should then show up somewhere in the reports menu, depending on the menu-path option in the report. The Tax Invoice and Balance Sheet reports (see below) put themselves into the Reports/Business sub-menu. | ||
Line 54: | Line 61: | ||
Note that s-expressions can be spread across more than one '<?scm ... ?>', | Note that s-expressions can be spread across more than one '<?scm ... ?>', | ||
− | for example: | + | for example:<SyntaxHighlight lang="HTML"> |
− | + | <?scm (if (> x 3) (begin ?>Bigger<?scm ) (begin ?>Smaller<?scm )) ?> | |
+ | </SyntaxHighlight> | ||
Each chunk of text outside a '<?scm ... ?>' pair ends up wrapped | Each chunk of text outside a '<?scm ... ?>' pair ends up wrapped | ||
Line 64: | Line 72: | ||
result (as a string which is passed back to the report-displaying part of GnuCash). | result (as a string which is passed back to the report-displaying part of GnuCash). | ||
− | For example, if the input file contained these lines: | + | For example, if the input file contained these lines:<SyntaxHighlight lang="HTML"> |
− | < | ||
<h1 align="center">Invoice <?scm:d invoiceid ?></h1> | <h1 align="center">Invoice <?scm:d invoiceid ?></h1> | ||
− | <?scm (for | + | <?scm (for entry in entries do ?> |
<p>Date: <?scm:d (entry date) ?>, description: <?scm:d (entry desc) ?> | <p>Date: <?scm:d (entry date) ?>, description: <?scm:d (entry desc) ?> | ||
− | <?scm | + | <?scm ) ?> |
− | </ | + | </SyntaxHighlight> |
− | the resulting script would look like: | + | |
− | < | + | the resulting script would look like:<SyntaxHighlight lang="Scheme"> |
(display "<h1 align=\"center\">Invoice ")(display invoiceid)(display "</h1>") | (display "<h1 align=\"center\">Invoice ")(display invoiceid)(display "</h1>") | ||
− | (for | + | (for entry in entries do |
(display "<p>Date: ")(display (entry date)) | (display "<p>Date: ")(display (entry date)) | ||
(display ", description: ")(display (entry desc)) | (display ", description: ")(display (entry desc)) | ||
− | + | ) | |
− | </ | + | </SyntaxHighlight> |
− | and the final result might be this: | + | and the final result might be this:<SyntaxHighlight lang="Scheme"> |
− | < | ||
<h1 align="center">Invoice 002345</h1> | <h1 align="center">Invoice 002345</h1> | ||
<p>Date: 04/03/2009, description: Widgets | <p>Date: 04/03/2009, description: Widgets | ||
<p>Date: 05/03/2009, description: Modified widgets | <p>Date: 05/03/2009, description: Modified widgets | ||
− | </ | + | </SyntaxHighlight> |
... | ... | ||
+ | |||
+ | === Custom for-loop syntax === | ||
+ | To simply looping over a list or map (which by default is a bit cumbersome in scheme), GnuCash defines a custom for-loop syntax, which works similar to for loops in some imperative languages. There are three forms of this syntax: For a single list, for multiple lists and for hashes. This for loop syntax should be available in all eguile-based reports (both in the report itself as well as in the eguile templates). This syntax is defined in eguile-utilities.scm, so it might be available in other parts of scheme as well. | ||
+ | |||
+ | ==== Single list ==== | ||
+ | <SyntaxHighlight lang="Scheme"> | ||
+ | (for <var> in <list> do <expr> ...) | ||
+ | </SyntaxHighlight> | ||
+ | |||
+ | This syntax loops over <tt><list></tt>, assigning each value to <tt><var></tt> in turn and evaluating all <expr>'s. In other words, it takes the first element of <list>, assigns it to <var>, evaluates each <expr>, takes the second element of <list>, etc. | ||
+ | |||
+ | Consider the following example:<SyntaxHighlight lang="Scheme"> | ||
+ | (for entry in (gncInvoiceGetEntries invoice) do | ||
+ | (displayEntry entry)) | ||
+ | </SyntaxHighlight> | ||
+ | |||
+ | This example would evaluate the "<tt>displayEntry</tt>" function for each entry in a given invoice. | ||
+ | |||
+ | ==== Multiple lists ==== | ||
+ | <SyntaxHighlight lang="Scheme"> | ||
+ | (for (<var> ...) in (<list> ...) do <expr> ...) | ||
+ | </SyntaxHighlight> | ||
+ | |||
+ | This syntax loops over all <tt><list></tt>'s in parallel, assigning each value to the corresponding <var> and evaluates all <expr>'s. In other words, it takes the first element of the first <list>, assigns it to the first <var>, takes the first element of the second <list>, assigns it to the second <var>, up to the last <list> and <var>. Then it evaluates each <expr> and starts over with the second element of each list, etc. | ||
+ | |||
+ | Consider the following example:<SyntaxHighlight lang="Scheme"> | ||
+ | (let ((descriptions (list "Company name" "Address" "Website")) | ||
+ | (values (list "Example Corp" "Example street 1" "http://example.org"))) | ||
+ | (for (desc val) in (descriptions values) do | ||
+ | (display desc) | ||
+ | (display ": ") | ||
+ | (display val) | ||
+ | (display "<br/>\n"))) | ||
+ | </SyntaxHighlight> | ||
+ | |||
+ | This example would output:<SyntaxHighlight lang="HTML"> | ||
+ | Website: http://example.org<br/> | ||
+ | Company name: Example Corp<br/> | ||
+ | Address: Example street 1<br/> | ||
+ | </SyntaxHighlight> | ||
+ | |||
+ | ==== Hashes (removed in Gnucash 3.7) ==== | ||
+ | <SyntaxHighlight lang="Scheme"> | ||
+ | (for <key> => <value> in <hash> do <expr> ...) | ||
+ | </SyntaxHighlight> | ||
+ | |||
+ | This syntax loops over all keys and values in a hash, assiging each key to <key> and each value to <value> in turn and evaluating all <expr>'s. Note that as always with hashes, the keys from the hash are looped in arbitrary order. | ||
+ | |||
+ | Consider the following example:<SyntaxHighlight lang="Scheme"> | ||
+ | (let ((company (make-hash-table 16))) | ||
+ | (hash-set! company "Company name" "Example Corp") | ||
+ | (hash-set! company "Address" "Example street 1") | ||
+ | (hash-set! company "Website" "http://example.org") | ||
+ | (for desc => val in company do | ||
+ | (display desc) | ||
+ | (display ": ") | ||
+ | (display val) | ||
+ | (display "<br/>\n"))) | ||
+ | </SyntaxHighlight> | ||
+ | |||
+ | This example would output exactly the same as the previous example. | ||
+ | |||
+ | This syntax was removed in Gnucash 3.7 (in this [https://github.com/Gnucash/gnucash/commit/e506b7c3325f09e84c1e5d9519e551cc49943535#diff-0e5f3da1af5a9af1929e52328ef4cf8c commit]). Instead, you can use the scheme built-in [https://www.gnu.org/software/guile/manual/html_node/Hash-Table-Reference.html hash-for-each], e.g.:<SyntaxHighlight lang="Scheme"> | ||
+ | (hash-for-each (lambda (desc val) ( | ||
+ | (display desc) | ||
+ | (display ": ") | ||
+ | (display val) | ||
+ | (display "<br/>\n") | ||
+ | )) company) | ||
+ | </SyntaxHighlight> | ||
=== ...the rest of this page will be here very soon === | === ...the rest of this page will be here very soon === | ||
Line 92: | Line 168: | ||
=== Internationalisation === | === Internationalisation === | ||
Always use _ rather than N_ in the template file. N_ can be used for report option names, help text, and default values. | Always use _ rather than N_ in the template file. N_ can be used for report option names, help text, and default values. | ||
+ | |||
+ | More precise N_ means NOP, no operation. It can be used to mark strings for translation e.g. in array declarations, but doesn't translate them. Then later the translation is called with _. More Details in [[Translation#Tips for Developers]]. | ||
+ | |||
== Available Reports == | == Available Reports == | ||
Line 99: | Line 178: | ||
==== Tax Invoice ==== | ==== Tax Invoice ==== | ||
{| | {| | ||
− | | Report file: || [ | + | | Report file: || [https://raw.githubusercontent.com/Gnucash/gnucash/maint/gnucash/report/business-reports/taxinvoice.scm taxinvoice.scm] |
|- | |- | ||
− | | Template file: || [ | + | | Template file: || [https://raw.githubusercontent.com/Gnucash/gnucash/maint/gnucash/report/business-reports/taxinvoice.eguile.scm taxinvoice.eguile.scm] |
|- | |- | ||
− | | CSS file | + | | CSS file: || [https://raw.githubusercontent.com/Gnucash/gnucash/maint/gnucash/report/business-reports/taxinvoice.css taxinvoice.css] |
− | |||
− | |||
|- | |- | ||
| Author: || [[User:ChrisDennis|ChrisDennis]] | | Author: || [[User:ChrisDennis|ChrisDennis]] | ||
|- | |- | ||
− | | Version: || 0. | + | | Version: || 0.03 |
|- | |- | ||
− | | Last update: || June | + | | Last update: || June 2018 |
|} | |} | ||
===== Features ===== | ===== Features ===== | ||
+ | * Included in the GnuCash source tree | ||
* Includes full company and customer details, including a company logo if required. | * Includes full company and customer details, including a company logo if required. | ||
* Automatically includes only relevant columns. For example, if none of the items on the invoice have discounts applied, then the discount columns will be omitted. | * Automatically includes only relevant columns. For example, if none of the items on the invoice have discounts applied, then the discount columns will be omitted. | ||
===== Issues ===== | ===== Issues ===== | ||
− | * | + | * No known issues |
− | |||
==== Balance Sheet ==== | ==== Balance Sheet ==== | ||
{| | {| | ||
− | | Report file: || [ | + | | Report file: || [https://raw.githubusercontent.com/Gnucash/gnucash/maint/gnucash/report/business-reports/balsheet-eg.scm balsheet-eg.scm] |
|- | |- | ||
− | | Template file: || [ | + | | Template file: || [https://raw.githubusercontent.com/Gnucash/gnucash/maint/gnucash/report/business-reports/balsheet-eg.eguile.scm balsheet-eg.eguile.scm] |
|- | |- | ||
− | | CSS file: || [ | + | | CSS file: || [https://raw.githubusercontent.com/Gnucash/gnucash/maint/gnucash/report/business-reports/balsheet-eg.css balsheet-eg.css] |
− | |||
− | |||
|- | |- | ||
| Author: || [[User:ChrisDennis|ChrisDennis]] | | Author: || [[User:ChrisDennis|ChrisDennis]] | ||
|- | |- | ||
− | | Version: || | + | | Version: || 1.54 |
|- | |- | ||
− | | Last update: || June | + | | Last update: || June 2018 |
|} | |} | ||
===== Features ===== | ===== Features ===== | ||
+ | * This report has been included in GnuCash. | ||
* Displays the balance sheet in a 'text book' style with rules under columns etc. | * Displays the balance sheet in a 'text book' style with rules under columns etc. | ||
* Uses the 'Assets = Equity + Liabilities' model. If there is demand for an 'Assets - Liabilities = Equity' version, then that could be done as a separate report, or by adding an option and some extra code to this one. | * Uses the 'Assets = Equity + Liabilities' model. If there is demand for an 'Assets - Liabilities = Equity' version, then that could be done as a separate report, or by adding an option and some extra code to this one. | ||
Line 147: | Line 223: | ||
* Only displays Imbalance and Orphan accounts if non-zero. | * Only displays Imbalance and Orphan accounts if non-zero. | ||
* Does not have a account selection option -- I don't see why you would want a balance sheet with some accounts missing. | * Does not have a account selection option -- I don't see why you would want a balance sheet with some accounts missing. | ||
− | * Does not deal with | + | * Does not deal with unrealised gains/losses. |
+ | ===== To Do ===== | ||
+ | * Option to put the currency symbol at the top of each column, rather than on every value. | ||
+ | * Option to put "Less: " in front of negative accounts, e.g. | ||
+ | Less: Drawings (£1000.00) | ||
===== Issues ===== | ===== Issues ===== | ||
− | * | + | * No known issues. |
− | + | ||
+ | ==Proposed Reports== | ||
+ | ===Cash Based Income Statement=== | ||
+ | Because of the importance and impacts this discussion moved to [[Cash Based Accounting]]. |
Latest revision as of 22:52, 4 February 2020
Contents
What is eguile?
eguile is a way of processing a template file to create a guile script.
More specifically, within GnuCash, guile is used to combine HTML and guile code to create a report, such as an invoice or balance sheet.
The GnuCash version of eguile is eguile-gnc.scm, written in early 2009 by Chris Dennis, and based on Neale Pickett's eguile.scm.
For example,
<h3><?scm:d coyname ?></h3>
<h2><?scm:d reportname ?> as at <?scm:d (gnc-print-date opt-date-tp) ?></h2>
would become
(display "<h3>")(display "Acme Tools Ltd.")(display "</h3>
<h2>)(display "Balance Sheet")(display " as at ")
(display "05/04/2009")(display "<h2">)
which is then evaluated as guile code to create the text of the report.
How to install an eguile report
To try out eguile-based reports, you first need to download eguile-gnc.scm and put it into the report folder. On my system -- Ubuntu -- that's /usr/share/gnucash/guile-modules/gnucash/report/eguile-gnc.scm. In general, it should go into the same folder as files such as fancy-invoice.scm.
- Note
- GnuCash >=2.3 already includes eguile-gnc.scm.
Install the report and its associated files in the usual way (see Custom Reports#Loading the Report for a full explanation).
Your report should start with a define-module statement like (define-module (gnucash report my-report-name))
and have a unique name (GnuCash 2.2.x and before)/a unique id (GnuCash 2.3.x and later).
(load "/path/to/my/.gnucash/report.scm")
If .gnucash/config.user does not already exist, just create it and add the new line.
For example, on a Linux system, for a user called 'chris', the Tax Invoice report can be installed by:
- Downloading taxinvoice.scm and taxinvoice.eguile.scm, and putting them into the /home/chris/.gnucash/ folder.
- Adding this line to /home/chris/.gnucash/config.user:
(load "/home/chris/.gnucash/taxinvoice.scm")
Restart GnuCash, and the report should then show up somewhere in the reports menu, depending on the menu-path option in the report. The Tax Invoice and Balance Sheet reports (see below) put themselves into the Reports/Business sub-menu.
How to create an eguile report
...
eguile syntax
Within what is otherwise an HTML source file, Guile/Scheme code is wrapped in '<?scm ... ?>' (whitespace is required after '<?scm' and before '?>')
'<?scm ... ?>' pairs can NOT be nested.
The optional :d modifier (i.e. '<?scm:d' ) is just a shortcut for '(display ... )', so '<?scm:d x ?>' is the same as '<?scm (display x) ?>'
Note that s-expressions can be spread across more than one '<?scm ... ?>',
for example:<?scm (if (> x 3) (begin ?>Bigger<?scm ) (begin ?>Smaller<?scm )) ?>
Each chunk of text outside a '<?scm ... ?>' pair ends up wrapped in a (display ... ), after having had double quotes etc. escaped.
The processing happens in two passes. Initially the input file is converted to a Guile script, and then that script is evaluated to produce the final result (as a string which is passed back to the report-displaying part of GnuCash).
For example, if the input file contained these lines: <h1 align="center">Invoice <?scm:d invoiceid ?></h1>
<?scm (for entry in entries do ?>
<p>Date: <?scm:d (entry date) ?>, description: <?scm:d (entry desc) ?>
<?scm ) ?>
(display "<h1 align=\"center\">Invoice ")(display invoiceid)(display "</h1>")
(for entry in entries do
(display "<p>Date: ")(display (entry date))
(display ", description: ")(display (entry desc))
)
<h1 align="center">Invoice 002345</h1>
<p>Date: 04/03/2009, description: Widgets
<p>Date: 05/03/2009, description: Modified widgets
...
Custom for-loop syntax
To simply looping over a list or map (which by default is a bit cumbersome in scheme), GnuCash defines a custom for-loop syntax, which works similar to for loops in some imperative languages. There are three forms of this syntax: For a single list, for multiple lists and for hashes. This for loop syntax should be available in all eguile-based reports (both in the report itself as well as in the eguile templates). This syntax is defined in eguile-utilities.scm, so it might be available in other parts of scheme as well.
Single list
(for <var> in <list> do <expr> ...)
This syntax loops over <list>, assigning each value to <var> in turn and evaluating all <expr>'s. In other words, it takes the first element of <list>, assigns it to , evaluates each <expr>, takes the second element of <list>, etc.
Consider the following example: (for entry in (gncInvoiceGetEntries invoice) do
(displayEntry entry))
This example would evaluate the "displayEntry" function for each entry in a given invoice.
Multiple lists
(for (<var> ...) in (<list> ...) do <expr> ...)
This syntax loops over all <list>'s in parallel, assigning each value to the corresponding and evaluates all <expr>'s. In other words, it takes the first element of the first <list>, assigns it to the first , takes the first element of the second <list>, assigns it to the second , up to the last <list> and . Then it evaluates each <expr> and starts over with the second element of each list, etc.
Consider the following example: (let ((descriptions (list "Company name" "Address" "Website"))
(values (list "Example Corp" "Example street 1" "http://example.org")))
(for (desc val) in (descriptions values) do
(display desc)
(display ": ")
(display val)
(display "<br/>\n")))
Website: http://example.org<br/>
Company name: Example Corp<br/>
Address: Example street 1<br/>
Hashes (removed in Gnucash 3.7)
(for <key> => <value> in <hash> do <expr> ...)
This syntax loops over all keys and values in a hash, assiging each key to <key> and each value to <value> in turn and evaluating all <expr>'s. Note that as always with hashes, the keys from the hash are looped in arbitrary order.
Consider the following example: (let ((company (make-hash-table 16)))
(hash-set! company "Company name" "Example Corp")
(hash-set! company "Address" "Example street 1")
(hash-set! company "Website" "http://example.org")
(for desc => val in company do
(display desc)
(display ": ")
(display val)
(display "<br/>\n")))
This example would output exactly the same as the previous example.
This syntax was removed in Gnucash 3.7 (in this commit). Instead, you can use the scheme built-in hash-for-each, e.g.: (hash-for-each (lambda (desc val) (
(display desc)
(display ": ")
(display val)
(display "<br/>\n")
)) company)
...the rest of this page will be here very soon
Internationalisation
Always use _ rather than N_ in the template file. N_ can be used for report option names, help text, and default values.
More precise N_ means NOP, no operation. It can be used to mark strings for translation e.g. in array declarations, but doesn't translate them. Then later the translation is called with _. More Details in Translation#Tips for Developers.
Available Reports
The following eguile-based reports are available. More will be contributed soon (hopefully).
Tax Invoice
Report file: | taxinvoice.scm |
Template file: | taxinvoice.eguile.scm |
CSS file: | taxinvoice.css |
Author: | ChrisDennis |
Version: | 0.03 |
Last update: | June 2018 |
Features
- Included in the GnuCash source tree
- Includes full company and customer details, including a company logo if required.
- Automatically includes only relevant columns. For example, if none of the items on the invoice have discounts applied, then the discount columns will be omitted.
Issues
- No known issues
Balance Sheet
Report file: | balsheet-eg.scm |
Template file: | balsheet-eg.eguile.scm |
CSS file: | balsheet-eg.css |
Author: | ChrisDennis |
Version: | 1.54 |
Last update: | June 2018 |
Features
- This report has been included in GnuCash.
- Displays the balance sheet in a 'text book' style with rules under columns etc.
- Uses the 'Assets = Equity + Liabilities' model. If there is demand for an 'Assets - Liabilities = Equity' version, then that could be done as a separate report, or by adding an option and some extra code to this one.
- One- or two-column layout.
- Negative values can be shown as, for example, '-£100.00' or '(£100.00)'.
- Deliberately has fewer options than the standard balance sheet. It makes 'intelligent' decisions about what to display. For example, parent accounts that are marked as placeholders and have a zero balance do not have their balance shown at all.
- Handles commodities/currencies in a basic way -- exchange rates are calculated as at the date of the balance sheet.
- A balancing entry (labelled 'Retained Earnings' by default) is calculated so that the sheet always balances.
- Only displays Imbalance and Orphan accounts if non-zero.
- Does not have a account selection option -- I don't see why you would want a balance sheet with some accounts missing.
- Does not deal with unrealised gains/losses.
To Do
- Option to put the currency symbol at the top of each column, rather than on every value.
- Option to put "Less: " in front of negative accounts, e.g.
Less: Drawings (£1000.00)
Issues
- No known issues.
Proposed Reports
Cash Based Income Statement
Because of the importance and impacts this discussion moved to Cash Based Accounting.