mirror of
https://github.com/simonmichael/hledger.git
synced 2024-12-26 03:42:25 +03:00
265 lines
12 KiB
Markdown
265 lines
12 KiB
Markdown
# Code
|
|
|
|
<div class=pagetoc>
|
|
|
|
<!-- 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.
|