hledger/doc/CODE.md

265 lines
12 KiB
Markdown
Raw Normal View History

# Code
2021-09-27 14:56:13 +03:00
<div class=pagetoc>
2021-09-27 14:56:13 +03:00
<!-- toc -->
</div>
hledger is a suite of applications, tools and libraries.
The main hledger code repository is [github.com/simonmichael/hledger](https://github.com/simonmichael/hledger)
(shortcut url `code.hledger.org`).
There are also various hledger add-ons maintained as separate projects with their own repos.
## hledger packages
Within the main repo, there are a number of separate cabal packages,
making it easier to pick and choose parts of hledger to install or to package.
They are:
### hledger-lib
[package](https://hackage.haskell.org/package/hledger-lib),
[code](https://github.com/simonmichael/hledger/tree/master/hledger-lib)
Core data models, parsing, standard reports, and utilities.
Most data types are defined in [Hledger.Data.Types](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html),
while functions that operate on them are defined in Hledger.Data.TYPENAME.
Under [Hledger.Read](https://github.com/simonmichael/hledger/tree/master/hledger-lib/Hledger/Read.hs)
are parsers for the supported input formats.
Data files are parsed into a
[Journal](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:Journal),
which contains a list of
[Transactions](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:Transaction),
each containing multiple
[Postings](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:Posting)
of some
[MixedAmount](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:MixedAmount)
(multiple
single-[CommoditySymbol](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:CommoditySymbol)
[Amounts](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:Amount))
to some
[AccountName](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:AccountName).
When needed, the Journal is further processed to derive a
[Ledger](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:Ledger),
which contains summed
[Accounts](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:Account).
In [Hledger.Reports](https://hackage.haskell.org/package/hledger-lib/docs/Hledger-Reports.html)
there are standard reports, which extract useful data from the Journal or Ledger.
Here's a diagram of the main data model:
<a href="https://hledger.org/images/data-model.png" class="highslide" onclick="return hs.expand(this)">
<img src="https://hledger.org/images/data-model.png" alt="diagram" title="main data types" style="max-width:100%;">
</a>
<!-- generated by plantuml from:
<uml>
hide empty members
hide circle
skinparam packageStyle Rect
Ledger *-- Journal
Ledger *-- "*" Account
note top of Ledger: A Journal and all its accounts with their balances.\nUsed for balance report
note top of Journal: A journal file and parsed transactions & directives.\nUsed for print & register reports
note bottom of Account: An account's name, balance (inclusive &\nexclusive), parent and child accounts
Account o-- "*" Account :subaccounts, parent
Journal o-- File
File o-- "*" File :include
Journal *-- "*" MarketPrice
Journal *-- "*" Transaction
MarketPrice -- Date
MarketPrice -- Amount
Transaction -- Date
Transaction *-- "*" Posting
Transaction o-- "*" Tag
Posting o- "*" Tag
Posting -- "0..1" Date
Account -- AccountName
Posting -- AccountName
Account -- "2" MixedAmount
Posting -- MixedAmount
MixedAmount *-- "*" Amount
Amount -- CommoditySymbol
Amount -- Quantity
Amount -- AmountPrice
Amount -- AmountStyle
</uml>
-->
### hledger
[package](https://hackage.haskell.org/package/hledger),
[code](https://github.com/simonmichael/hledger/tree/master/hledger),
[manual](https://hledger.org/hledger.html)
hledger's command line interface, and command line options and utilities for other hledger tools.
Try tracing the execution of a hledger command:
1. [Hledger.Cli.Main:main](https://github.com/simonmichael/hledger/blob/master/hledger/Hledger/Cli/Main.hs#L302)
parses the command line to select a command, then
2. gives it to
[Hledger.Cli.Utils:withJournalDo](https://github.com/simonmichael/hledger/blob/master/hledger/Hledger/Cli/Utils.hs#L73),
which runs it after doing all the initial parsing.
3. Parsing code is under
[hledger-lib:Hledger.Read](https://github.com/simonmichael/hledger/tree/master/hledger-lib/Hledger/Read.hs),
eg [Hledger.Read.JournalReader](https://github.com/simonmichael/hledger/tree/master/hledger-lib/Hledger/Read/JournalReader.hs).
4. Commands extract useful information from the parsed data model using
[hledger-lib:Hledger.Reports](https://github.com/simonmichael/hledger/tree/master/hledger-lib/Hledger/Reports),
and
5. render in plain text for console output (or another output format, like CSV).
6. Everything uses the data types and utilities from
[hledger-lib:Hledger.Data](https://github.com/simonmichael/hledger/tree/master/hledger-lib/Hledger/Data)
and [hledger-lib:Hledger.Utils](https://github.com/simonmichael/hledger/blob/master/hledger-lib/Hledger/Utils.hs).
### hledger-ui
[package](https://hackage.haskell.org/package/hledger-ui),
[code](https://github.com/simonmichael/hledger/tree/master/hledger-ui),
[manual](https://hledger.org/hledger-ui.html)
A terminal interface.
### hledger-web
[package](https://hackage.haskell.org/package/hledger-web),
[code](https://github.com/simonmichael/hledger/tree/master/hledger-web),
[manual](https://hledger.org/hledger-web.html)
A web interface.
hledger-web starts a web server built with the yesod framework,
and (by default) opens a web browser view on it.
It reads the journal file(s) at startup and again whenever they change.
It can also write (append) new transactions to the journal file.
There are two main views, which can be filtered with
[queries](https://hledger.org/hledger.html#queries):
- [/journal](https://demo.hledger.org/journal), showing general journal entries (like `hledger print`)
- [/register](https://demo.hledger.org/register?q=inacct:Expenses:Food),
showing transactions affecting an account (slightly different from
hledger's [register](https://hledger.org/hledger.html#register) command, which shows postings).
There is also:
- a sidebar (toggled by pressing `s`) showing the chart of accounts (like `hledger balance`)
- an [add form](https://demo.hledger.org/journal?add=1) for adding new transactions (press `a`)
- a help dialog showing quick help and keybindings (press `h` or click ?)
Most of the action is in
- [config/routes](https://github.com/simonmichael/hledger/tree/master/hledger-web/config/routes)
- [templates/default-layout-wrapper.hamlet](https://github.com/simonmichael/hledger/tree/master/hledger-web/templates/default-layout-wrapper.hamlet)
- [Foundation](https://github.com/simonmichael/hledger/tree/master/hledger-web/Foundation.hs)
- [Handler.*](https://github.com/simonmichael/hledger/tree/master/hledger-web/Handler)
- [static/hledger.js](https://github.com/simonmichael/hledger/tree/master/hledger-web/static/hledger.js)
- [static/hledger.css](https://github.com/simonmichael/hledger/tree/master/hledger-web/static/hledger.css)
Handler module and function names end with R, like the yesod-generated route type they deal with.
Dynamically generated page content is mostly inline hamlet.
Lucius/Julius files and widgets generally are not used, except for the default layout.
Here are some ways to run it during development:
- `yesod devel`: runs in developer mode, rebuilds automatically when config, template, static or haskell files change
(but only files in the hledger-web package):
```cli
$ (cd hledger-web; yesod devel)
```
- [yesod-fast-devel](https://hackage.haskell.org/package/yesod-fast-devel)
may be a good alternative, also reloads the browser page
- `stack ghci`: runs the server in developer mode from GHCI.
Changes to static files like hledger.js will be visible on page reload;
to see other changes, restart it as shown.
```cli
$ (cd hledger-web; stack ghci hledger-web)
hledger-web> :main --serve # restart: ctrl-c, :r, enter, ctrl-p, ctrl-p, enter
```
- `just ghci-web`: runs the server in developer mode from GHCI, also
interprets the hledger-lib and hledger packages so that :reload picks
up changes in those packages too:
```cli
$ just ghci-web
ghci> :main --serve
```
(This rule also creates symbolic links to hledger-web's `config`, `messages`, `static` and `templates`
directories, needed in developer mode, so it can run from the top directory. This may not work on Windows.)
## Quality
Relevant tools include:
- unit tests
- functional tests
- performance tests
- documentation tests
- ui tests (manual)
- installation tests
- code reviews
## Code review
- Code review party 2014/7/21-25:
[discussion](https://thread.gmane.org/gmane.comp.finance.ledger.hledger/1070)<!-- missing ,
[log](https://hledger.org/static/irc-20140725-code-review.html) -->
- Dev sprint/party 2015/10/10:
[discussion](https://thread.gmane.org/gmane.comp.finance.ledger.hledger/1254)<!-- ircbrowse down ,
[pre-chat](https://ircbrowse.net/day/hledger/2015/10/10),
[log](https://ircbrowse.net/day/hledger/2015/10/11) -->
## Code docs
Haddock comments are the standard way of attaching docs (and sometimes small tests)
to a haskell definition. They are used pervasively in the hledger codebase.
From [#2222](https://github.com/simonmichael/hledger/pull/2222):
I tend to add a line of english description to most things.
They can be a big help to someone else debugging/improving code later.
> *I use Haddock but I do not like to tell obvious things in Haddock comments.
> (And if there are surprising things I try to eliminate the surprises instead.)*
I hear you! This is of course a recurring debate among programmers.
I agree that obviously redundant haddocks that add no value are to be avoided.
In the hledger codebase, there's no hard requirement for haddocks.
And sometimes the bar can be lower, eg in single-purpose less-frequently-developed modules like FODS.hs.
But I believe it's always worth at least considering them when adding code, and it's something I consider when reviewing code.
I'll give some reasons why I think so, for the record (and maybe they'll persuade you just a little).
Haddocks help guide and anchor the developer while writing or changing code.
They are the cheapest kind of doc, spec and test suite.
They also create a place to add actual doctests, now or later.
Haddocks can be helpful to contributors who are not expert haskellers, which happens quite often in the hledger project.
The human language descriptions can complement the code, reducing cognitive effort and helping with mental chunking.
I think people find the hledger's code, with its pervasive haddocks, above average in readability.
(And I think we've had more successful contributions as a result.)
Haddocks can also help experienced developers move faster.
Code which seems quite clear and obvious when you are writing it is often less obvious to oneself a few weeks or years later.
And certainly it can be less obvious to others than we might think.
When debugging or coding we are often mentally stretched and we would prefer to conserve brainpower for the main task.
In my experience debugging/writing/changing hledger code,
- Number of times I've regretted seeing a haddock comment attached to some code: almost zero.
- Effort to remove an excessive haddock: almost zero.
- Number of times I've been trying to understand some contributed code and haddocks would have saved my time: quite a few.
- Number of times I've appreciated code haddocks and found them helpful: many.
- Number of times I've found errors or staleness in haddocks: not often.
- Effort to fix wrong haddocks, or to write new ones: usually very low. And if it's high, it's usually very worthwhile because it alerts me to a confusion in the code or clarifies my thinking.
hledger is a documentation-driven project, with docs in general being a top priority.
I think this is one reason it has held together and kept improving over a long period.