1507 lines
		
	
	
		
			110 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			1507 lines
		
	
	
		
			110 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| <!-- consolidating dev docs from wiki, https://github.com/simonmichael/hledger/issues/920 WIP -->
 | ||
| $toc$
 | ||
| 
 | ||
| # Contributor Guide
 | ||
| 
 | ||
| <br clear=all>
 | ||
| 
 | ||
| ## Quick Links
 | ||
| 
 | ||
| | | |
 | ||
| |-------------------------|----------------------------------------------------------------------------|
 | ||
| | IRC                     | [#hledger](https://kiwiirc.com/nextclient/#ircs://irc.freenode.net/#hledger), [chat log](http://ircbrowse.net/browse/hledger). Also: [#hledger-bots](https://kiwiirc.com/nextclient/#ircs://irc.freenode.net/hledger-bots), [#ledger](https://kiwiirc.com/nextclient/#ircs://irc.freenode.net/hledger), [#beancount](https://kiwiirc.com/nextclient/#ircs://irc.freenode.net/hledger) |
 | ||
| | Mail list               | [list.hledger.org](http://list.hledger.org) |
 | ||
| | Twitter                 | [#hledger](https://twitter.com/search?q=%23hledger&src=typd&f=realtime). Also: [#plaintextaccounting](https://twitter.com/search?q=%23plaintextaccounting&src=typd&f=realtime), <a href="https://twitter.com/ledgertips">@LedgerTips</a>, [#ledgercli](https://twitter.com/search?q=%23ledgercli&src=typd&f=realtime) |
 | ||
| | Reddit                  | [/r/plaintextaccounting](https://www.reddit.com/r/plaintextaccounting/) |
 | ||
| | Stack Exchange          | [money.stackexchange.com?hledger](https://money.stackexchange.com/search?q=hledger) |
 | ||
| | Hacker News             | [hledger mentions](https://hn.algolia.com/?query=hledger&sort=byDate&prefix&page=0&dateRange=all&type=all) |
 | ||
| | hledger-web demo   | [demo.hledger.org](http://demo.hledger.org) |
 | ||
| | hledger-api demo        | [api-demo.hledger.org/api/v1/accounts](http://api-demo.hledger.org/api/v1/accounts), [api-demo.hledger.org/swagger.json](http://api-demo.hledger.org/swagger.json), [in swagger editor](http://editor2.swagger.io/#/?import=api-demo.hledger.org/swagger.json&no-proxy) <br>[unfinished angular sample app](http://api-demo.hledger.org) ([code](https://github.com/simonmichael/hledger/tree/master/hledger-api/examples/angular))
 | ||
| | hledger interactive demo | https://hledger.alhur.es  (hledger compiled to js)
 | ||
| | Trello                  | [old wishlist planning board](http://trello.hledger.org) |
 | ||
| | Github                  | [simonmichael/hledger](https://github.com/simonmichael/hledger) (shortcut: code.hledger.org), [forks](http://forked.yannick.io/simonmichael/hledger) <br> [commits](http://github.com/simonmichael/hledger/commits), <!-- [unreleased commits](https://github.com/simonmichael/hledger/compare/0.23...master), [release branch commits](https://github.com/simonmichael/hledger/compare/master...0.23), --> [COMMITS!](http://starlogs.net/#simonmichael/hledger) <br> [open bugs](http://bugs.hledger.org), [open wishes](http://wishes.hledger.org), [open unknowns](https://github.com/simonmichael/hledger/issues?utf8=✓&q=is%3Aissue%20is%3Aopen%20-label%3A%22A%20BUG%22%20-label%3A%22A%20WISH%22%20), [open pull requests](http://prs.hledger.org), [all issues](https://github.com/simonmichael/hledger/issues?q=) <br> [issues with bounty tag](https://github.com/simonmichael/hledger/issues?q=label:bounty), [bountysource bounties](https://github.com/simonmichael/hledger/issues?q=%22Add%20to%20the%20bounty%20at%20Bountysource%22%20OR%20%22claim%20the%20bounty%20on%20Bountysource%22%20OR%20%22bounty%20on%20this%20issue%20has%20been%20claimed%20at%20Bountysource%22%20), [codemill bounties](https://github.com/simonmichael/hledger/issues?q=codemill), [codefund bounties](https://github.com/simonmichael/hledger/issues?utf8=✓&q=codefund) <br> [stars.hledger.org](http://stars.hledger.org):  <a class="github-button" href="https://github.com/simonmichael/hledger" data-icon="octicon-star" data-count-href="/simonmichael/hledger/stargazers" data-count-api="/repos/simonmichael/hledger#stargazers_count" data-count-aria-label="# stargazers on GitHub" aria-label="Star simonmichael/hledger on GitHub"></a> our rank among ~30k starred haskell projects:<br>2016: ->#71, 2017: ->#54, 2018 ->#53) <br> [github projects](https://github.com/simonmichael/hledger/projects), [waffle.io planning board](https://waffle.io/simonmichael/hledger?source=simonmichael%2Fhledger) <br> [<img width=520 height=170 title="..." src="https://graphs.waffle.io/simonmichael/hledger/throughput.svg">](https://waffle.io/simonmichael/hledger/metrics) |
 | ||
| | Travis CI               | [](https://travis-ci.org/simonmichael/hledger/builds)
 | ||
| | Appveyor CI             | [](https://ci.appveyor.com/project/simonmichael/hledger/history) 
 | ||
| | Hackage                 | <a name=hackage></a>packages: [hledger-lib](http://hackage.haskell.org/package/hledger-lib), [hledger](http://hackage.haskell.org/package/hledger), [hledger-ui](http://hackage.haskell.org/package/hledger-ui), [hledger-web](http://hackage.haskell.org/package/hledger-web), [hledger-api](http://hackage.haskell.org/package/hledger-api), [hledger-diff](http://hackage.haskell.org/package/hledger-diff), [hledger-iadd](http://hackage.haskell.org/package/hledger-iadd), [hledger-interest](http://hackage.haskell.org/package/hledger-interest), [hledger-irr](http://hackage.haskell.org/package/hledger-irr), [\*hledger\*](http://hackage.haskell.org/packages/search?terms=hledger) <!-- [](http://hackage.haskell.org/package/hledger) --> <br> diffs: [hledger-lib](http://hdiff.luite.com/cgit/hledger-lib/diff), [hledger](http://hdiff.luite.com/cgit/hledger/diff), [hledger-ui](http://hdiff.luite.com/cgit/hledger-ui/diff), [hledger-web](http://hdiff.luite.com/cgit/hledger-web/diff), [hledger-api](http://hdiff.luite.com/cgit/hledger-api/diff) <br> build status: [hledger-lib](http://matrix.hackage.haskell.org/package/hledger-lib), [hledger](http://matrix.hackage.haskell.org/package/hledger), [hledger-ui](http://matrix.hackage.haskell.org/package/hledger-ui), [hledger-web](http://matrix.hackage.haskell.org/package/hledger-web), [hledger-api](http://matrix.hackage.haskell.org/package/hledger-api) <br> reverse deps: [hledger-lib](http://packdeps.haskellers.com/reverse/hledger-lib), [hledger](http://packdeps.haskellers.com/reverse/hledger), [hledger-ui](http://packdeps.haskellers.com/reverse/hledger-ui), [hledger-web](http://packdeps.haskellers.com/reverse/hledger-web), [hledger-api](http://packdeps.haskellers.com/reverse/hledger-api) <br> bounds status:<br>[](http://packdeps.haskellers.com/feed?needle=hledger-lib) [](http://packdeps.haskellers.com/feed?needle=hledger) [](http://packdeps.haskellers.com/feed?needle=hledger-ui) [](http://packdeps.haskellers.com/feed?needle=hledger-web) [](http://packdeps.haskellers.com/feed?needle=hledger-api) |
 | ||
| | Stackage                | [build-constraints.yaml](https://github.com/fpco/stackage/blob/master/build-constraints.yaml) <br> [open hledger-related issues](https://github.com/fpco/stackage/search?q=hledger+is%3Aopen&type=Issues) <br> packages: [hledger-lib](https://www.stackage.org/package/hledger-lib), [hledger](https://www.stackage.org/package/hledger), [hledger-ui](https://www.stackage.org/package/hledger-ui), [hledger-web](https://www.stackage.org/package/hledger-web), [hledger-api](https://www.stackage.org/package/hledger-api)<br> versions: [hledger-lib](https://www.stackage.org/package/hledger-lib/snapshots), [hledger](https://www.stackage.org/package/hledger/snapshots), [hledger-ui](https://www.stackage.org/package/hledger-ui/snapshots), [hledger-web](https://www.stackage.org/package/hledger-web/snapshots), [hledger-api](https://www.stackage.org/package/hledger-api/snapshots) <br>[](https://repology.org/metapackage/hledger)|
 | ||
| | Repology                | [quick hledger packaging status](https://repology.org/metapackage/hledger/badges), [detailed \*hledger\* packaging status](https://repology.org/metapackages/?search=hledger) <br>[](https://repology.org/metapackage/hledger)
 | ||
| | Debian                  | source packages: [haskell-hledger-lib](http://tracker.debian.org/pkg/haskell-hledger-lib), [bugs](https://bugs.debian.org/cgi-bin/pkgreport.cgi?package=haskell-hledger-lib), [haskell-hledger](http://tracker.debian.org/pkg/haskell-hledger), [bugs](https://bugs.debian.org/cgi-bin/pkgreport.cgi?package=haskell-hledger), [haskell-hledger-ui](http://tracker.debian.org/pkg/haskell-hledger-ui), [bugs](https://bugs.debian.org/cgi-bin/pkgreport.cgi?package=haskell-hledger-ui), [haskell-hledger-web](http://tracker.debian.org/pkg/haskell-hledger-web), [bugs](https://bugs.debian.org/cgi-bin/pkgreport.cgi?package=haskell-hledger-web) <br> binary packages: <br>  stable: [hledger](https://packages.debian.org/stable/hledger), [bugs](https://bugs.debian.org/cgi-bin/pkgreport.cgi?package=hledger;dist=stable), <!-- [hledger-ui](https://packages.debian.org/stable/hledger-ui), [bugs](https://bugs.debian.org/cgi-bin/pkgreport.cgi?package=hledger-ui;dist=stable), --> [hledger-web](https://packages.debian.org/stable/hledger-web), [bugs](https://bugs.debian.org/cgi-bin/pkgreport.cgi?package=hledger-web;dist=stable) <br>  testing: [hledger](https://packages.debian.org/testing/hledger), [bugs](https://bugs.debian.org/cgi-bin/pkgreport.cgi?package=hledger;dist=testing), <!-- [hledger-ui](https://packages.debian.org/testing/hledger-ui), [bugs](https://bugs.debian.org/cgi-bin/pkgreport.cgi?package=hledger-ui;dist=testing), --> [hledger-web](https://packages.debian.org/testing/hledger-web), [bugs](https://bugs.debian.org/cgi-bin/pkgreport.cgi?package=hledger-web;dist=testing) <br>  unstable: [hledger](https://packages.debian.org/unstable/hledger), [bugs](https://bugs.debian.org/cgi-bin/pkgreport.cgi?package=hledger;dist=unstable), [hledger-ui](https://packages.debian.org/unstable/hledger-ui), [bugs](https://bugs.debian.org/cgi-bin/pkgreport.cgi?package=hledger-ui;dist=unstable), [hledger-web](https://packages.debian.org/unstable/hledger-web), [bugs](https://bugs.debian.org/cgi-bin/pkgreport.cgi?package=hledger-web;dist=unstable) <br>  experimental: [hledger](https://packages.debian.org/experimental/hledger), [bugs](https://bugs.debian.org/cgi-bin/pkgreport.cgi?package=hledger;dist=experimental), [hledger-ui](https://packages.debian.org/experimental/hledger-ui), [bugs](https://bugs.debian.org/cgi-bin/pkgreport.cgi?package=hledger-ui;dist=experimental), [hledger-web](https://packages.debian.org/experimental/hledger-web), [bugs](https://bugs.debian.org/cgi-bin/pkgreport.cgi?package=hledger-web;dist=experimental) <br>  all: [\*hledger\*](https://packages.debian.org/search?searchon=names&keywords=hledger) <br>[Package Tracking System help](https://www.debian.org/doc/manuals/developers-reference/resources.html#pkg-tracking-system) <br> popcon votes: [hledger](https://qa.debian.org/popcon.php?package=haskell-hledger), [hledger-ui](https://qa.debian.org/popcon.php?package=haskell-hledger-ui), [hledger-web](https://qa.debian.org/popcon.php?package=haskell-hledger-web), hledger active users:<br>[<img width=400 height=300 title="..." src="https://qa.debian.org/cgi-bin/popcon-png?packages=hledger&show_installed=0&show_vote=1&show_old=0&show_recent=0&show_nofiles=0&want_percent=0&want_legend=1&want_ticks=1&from_date=&to_date=&hlght_date=&date_fmt=%25Y">](https://qa.debian.org/popcon-graph.php?packages=hledger&show_vote=on&want_legend=on&want_ticks=on&from_date=&to_date=&hlght_date=&date_fmt=%25Y&beenhere=1) |
 | ||
| | Ubuntu                  | source packages: [haskell-hledger-lib](https://launchpad.net/ubuntu/+source/haskell-hledger-lib), [bugs](https://bugs.launchpad.net/ubuntu/+source/haskell-hledger-lib), [haskell-hledger](https://launchpad.net/ubuntu/+source/haskell-hledger), [bugs](https://bugs.launchpad.net/ubuntu/+source/haskell-hledger), [haskell-hledger-ui](https://launchpad.net/ubuntu/+source/haskell-hledger-ui), [bugs](https://bugs.launchpad.net/ubuntu/+source/haskell-hledger-ui), [haskell-hledger-web](https://launchpad.net/ubuntu/+source/haskell-hledger-web), [bugs](https://bugs.launchpad.net/ubuntu/+source/haskell-hledger-web) <br> binary packages: [\*hledger\*](http://packages.ubuntu.com/search?suite=all&searchon=names&keywords=hledger) |
 | ||
| | Gentoo                  | [hledger](http://gpo.zugaina.org/dev-haskell/hledger), [hledger-web](http://gpo.zugaina.org/dev-haskell/hledger-web), [\*hledger\*](http://gpo.zugaina.org/Search?search=hledger) |
 | ||
| | Fedora                  | [hledger](https://apps.fedoraproject.org/packages/hledger), [\*hledger\*](https://apps.fedoraproject.org/packages/s/hledger), [hledger (package db)](https://admin.fedoraproject.org/pkgdb/package/hledger/), [Haskell SIG](http://fedoraproject.org/wiki/Haskell_SIG) |
 | ||
| | Void Linux              | [package search](https://voidlinux.org/packages/) -> hledger |
 | ||
| | Nix                     | [\*hledger\*](http://hydra.nixos.org/search?query=hledger) |
 | ||
| | Homebrew                | [hledger](https://formulae.brew.sh/formula/hledger) |
 | ||
| | Sandstorm               | [hledger web app & reviews](https://apps.sandstorm.io/app/8x12h6p0x0nrzk73hfq6zh2jxtgyzzcty7qsatkg7jfg2mzw5n90), [issues](https://github.com/simonmichael/hledger/issues?utf8=✓&q=label%3A%22platform%3A%20sandstorm%22%20)
 | ||
| | Reference               | [GHC Can I Use](http://damianfral.github.io/ghcaniuse/) |
 | ||
| 
 | ||
| <!-- list the debian packages for clarity:
 | ||
| 3 source:
 | ||
| haskell-hledger-lib
 | ||
| haskell-hledger
 | ||
| haskell-hledger-web
 | ||
| 8 binary:
 | ||
| hledger
 | ||
| hledger-web
 | ||
| libghc-hledger-dev
 | ||
| libghc-hledger-doc
 | ||
| libghc-hledger-prof
 | ||
| libghc-hledger-lib-dev
 | ||
| libghc-hledger-lib-doc
 | ||
| libghc-hledger-lib-prof
 | ||
| -->
 | ||
| 
 | ||
| 
 | ||
| ## About the project
 | ||
| 
 | ||
| ### Mission
 | ||
| 
 | ||
| Why was hledger created ?
 | ||
| 
 | ||
| Mainly:
 | ||
| 
 | ||
| - to provide a more usable, robust, documented, cross-platform-installable version of Ledger for users
 | ||
| - to provide a more maintainable and hackable version of Ledger for developers 
 | ||
| 
 | ||
| Also:
 | ||
| 
 | ||
| - to provide a useful library and toolbox for finance-minded haskell programmers
 | ||
| - to explore the suitability of Haskell for such applications
 | ||
| - to experiment with building a successful time-and-money-solvent project in a thriving ecosystem of financial software projects
 | ||
| 
 | ||
| What is the hledger project's current mission ?
 | ||
| 
 | ||
| 1. Provide peace of mind: bring clarity, relief, and peace of mind to folks stressed, confused, overwhelmed by finances.
 | ||
| 2. Educate and empower: help individuals and communities achieve clarity, accountability and mastery with money and time.
 | ||
| 
 | ||
| ### Roles and activities
 | ||
| 
 | ||
| - newcomer/potential user
 | ||
| - user
 | ||
| - library user
 | ||
| - field tester
 | ||
| - bug wrangler
 | ||
| - support
 | ||
| - documentor
 | ||
| - qa
 | ||
| - developer
 | ||
| - packager
 | ||
| - communicator
 | ||
| - project manager
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| ## Getting started
 | ||
| 
 | ||
| New contributors are always welcome in the hledger project. 
 | ||
| Jump in! Or [ask us](http://hledger.org/docs.html#helpfeedback) to help you find a task.
 | ||
| 
 | ||
| ### Funder
 | ||
| 
 | ||
| Become a financial backer to
 | ||
| sustain and grow this project,
 | ||
| increase your influence,
 | ||
| express gratitude,
 | ||
| build prosperity consciousness,
 | ||
| and help transform world finance!
 | ||
| 
 | ||
| - Use the donate links on the [home page](/)
 | ||
| - Configure a recurring donation
 | ||
| - Contribute or pledge bounties on issues you care about
 | ||
| - Ask your organization to contribute
 | ||
| - Work on project sustainability, accountability, fundraising
 | ||
| 
 | ||
| ### Tester
 | ||
| 
 | ||
| - Test installation on platforms you have access to
 | ||
| - Test examples, advice, and links in the docs
 | ||
| - Run the latest release or developer build in daily use
 | ||
| - Run [[tests|Developer-workflows#run-package-tests]]
 | ||
| - Run [[benchmarks|Developer-workflows#run-package-benchmarks]]
 | ||
| - Report packaging, documentation, UX, functional, performance issues
 | ||
| - Report and help analyse problems via irc/mail list/bug tracker
 | ||
| 
 | ||
| When reporting bugs, don't forget to search the tracker for a similar bug report.
 | ||
| Otherwise, open a new bug by clicking "New issue", or <http://bugs.hledger.org/new>.
 | ||
| 
 | ||
| Enhancement requests are sometimes added to the tracker,but for these consider using
 | ||
| the IRC channel and mail list (see [Getting help](/docs.html#getting-help)).
 | ||
| Both are archived and linkable, so the idea won't be lost.
 | ||
| There is also a collection of wishes at the old [trello board](http://trello.hledger.org).
 | ||
| 
 | ||
| ### Technical Writer
 | ||
| 
 | ||
| - get familiar with the website and documentation online, review and test
 | ||
| - get familiar with the site/doc source files (see [Shake.hs](#shake))
 | ||
| - get the latest hledger source
 | ||
| - send patches with names prefixed with "doc: " (or "site: ")
 | ||
| 
 | ||
| ### Graphics Designer
 | ||
| 
 | ||
| - more/better logos & graphics
 | ||
| - illustrations and diagrams
 | ||
| - web design mockups for home page, site, hledger-web UI
 | ||
| 
 | ||
| <!-- ### Product Designer -->
 | ||
| ### Communicator
 | ||
| 
 | ||
| Marketing and market understanding is vital.
 | ||
| 
 | ||
| - clarify project goals, value proposition, brand, mission, story
 | ||
| - monitor product-market fit
 | ||
| - identify new opportunities
 | ||
| - influence developer priorities
 | ||
| - spread the word!
 | ||
| 
 | ||
| ### Maintainer
 | ||
| 
 | ||
| #### Help with issue management
 | ||
| 
 | ||
| - watch tracker activity, report status
 | ||
| - apply/update labels where needed
 | ||
| - follow up on dormant issues
 | ||
| - facilitate a consistently good bug-reporting & PR-contributing experience
 | ||
| 
 | ||
| #### Help with packaging
 | ||
| 
 | ||
| - package hledger for linux distros, macports, etc.
 | ||
| - develop mac/windows installers
 | ||
| - find and assist distro packagers/installer developers
 | ||
| 
 | ||
| #### Help with project management
 | ||
| 
 | ||
| - clarify/update goals and principles
 | ||
| - monitor, report on project progress and performance
 | ||
| - research, compare and report on successful projects, related projects
 | ||
| - identify collaboration opportunities
 | ||
| - marketing, communication, outreach
 | ||
| - release management, roadmap planning
 | ||
| 
 | ||
| ### Developer
 | ||
| 
 | ||
| See [[Developer workflows]]
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| ## Make
 | ||
| 
 | ||
| A Makefile is provided to make common developer tasks easy to remember,
 | ||
| and to insulate us a little from the ever-evolving Haskell tools ecosystem.
 | ||
| Using it is entirely optional, but recommended.
 | ||
| You'll need [GNU Make](http://www.gnu.org/software/make) installed.
 | ||
| 
 | ||
| The Makefile contains a fair amount of obsolete cruft and needs cleanup. Some tasks (docs, website) are now handled by the [[Shake]] file instead.
 | ||
| 
 | ||
| The Makefile is self-documenting. Run `make` to see a list of the main make rules:
 | ||
| 
 | ||
| ```shell
 | ||
| $ make
 | ||
| Makefile:37: -------------------- hledger make rules --------------------
 | ||
| Makefile:39: make [help] -- list documented rules in this makefile. make -n RULE shows more detail.
 | ||
| Makefile:204: (INSTALLING:)
 | ||
| Makefile:206: make install -- download dependencies and install hledger executables to ~/.local/bin or equivalent (with stack)
 | ||
| Makefile:231: (BUILDING:)
 | ||
| Makefile:235: make build -- download dependencies and build hledger executables (with stack)
 | ||
| Makefile:304: make hledgerdev -- quickly build the hledger executable (with ghc and -DDEVELOPMENT)
 | ||
| ...
 | ||
| ```
 | ||
| 
 | ||
| To see what a make rule will do without actually doing it, use the `-n` flag:
 | ||
| 
 | ||
| ```shell
 | ||
| $ make build -n
 | ||
| stack build
 | ||
| ```
 | ||
| ```shell
 | ||
| $ make test -n
 | ||
| (stack test \
 | ||
| 		&& echo pkgtest PASSED) || echo pkgtest FAILED
 | ||
| (stack exec hledger test \
 | ||
| 		&& echo builtintest PASSED) || echo builtintest FAILED
 | ||
| (COLUMNS=80 PATH=`pwd`/bin:/home/simon/src/hledger/bin:/home/simon/.local/bin:/home/simon/.cabal/bin:/opt/ghc/7.10.1/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/var/lib/gems/1.9.1/bin stack exec -- shelltest --execdir -- -j16 --hide-successes tests \
 | ||
| 		&& echo functest PASSED) || echo functest FAILED
 | ||
| ```
 | ||
| 
 | ||
| 
 | ||
| ## Shake
 | ||
| 
 | ||
| `Shake.hs` in the top directory complements the Makefile; it is used for some more complex tasks, such as building documentation and the web site.
 | ||
| 
 | ||
| Compile it: 
 | ||
| 
 | ||
|     ./Shake.hs   # or, make Shake
 | ||
| 
 | ||
| See help:
 | ||
| 
 | ||
|     ./Shake
 | ||
| 
 | ||
| 
 | ||
| ## Developer workflows
 | ||
| 
 | ||
| ### Get developer tools
 | ||
| 
 | ||
| Ensure [`stack`](https://haskell-lang.org/get-started) is installed
 | ||
| (or if you’re a [cabal](https://www.haskell.org/cabal/) expert, feel free to use that.)
 | ||
| 
 | ||
| Ensure [`git`](http://git-scm.com) is installed. On Windows, it comes with stack.
 | ||
| 
 | ||
| Here are some useful optional tools:
 | ||
| 
 | ||
| - [GNU Make](http://www.gnu.org/software/make): to use the convenient [Make rules](#make).
 | ||
| - [`entr`](http://www.entrproject.org/) runs arbitrary commands when files change.
 | ||
| - [`ghcid`](http://hackage.haskell.org/package/ghcid) gives real-time GHC feedback as you make code changes.
 | ||
| - [`shelltestrunner`](http://hackage.haskell.org/package/shelltestrunner) runs hledger's functional tests.
 | ||
| - [`quickbench`](http://hackage.haskell.org/package/quickbench) measures and reports time taken by commands.
 | ||
| - [`hasktags`](http://hackage.haskell.org/package/hasktags) generates tag files for quick code navigation in editors like Emacs and vim.
 | ||
| - For browsing and editing Haskell code, popular tools include: Emacs, Vim, IDEA, VS Code, Atom..
 | ||
| 
 | ||
| Eg:
 | ||
| 
 | ||
|     stack install ghcid shelltestrunner quickbench hasktags
 | ||
|     brew install entr
 | ||
| 
 | ||
| ### Get the code
 | ||
| 
 | ||
|     git clone https://github.com/simonmichael/hledger
 | ||
|     cd hledger
 | ||
| 
 | ||
| ### Review code
 | ||
| 
 | ||
| - review and discuss new [pull requests](http://prs.hledger.org) and commits on github
 | ||
| - build hledger and test the latest changes in your own repo
 | ||
| - read the existing [code docs and source](#quick-links)
 | ||
| - send feedback or discuss via [IRC or mail list](/docs.html#helpfeedback)
 | ||
| 
 | ||
| ### Build in place
 | ||
| 
 | ||
| See also http://hledger.org/download.html#c.-build-the-development-version .
 | ||
| 
 | ||
|     stack build    # hledger hledger-ui ...
 | ||
| 
 | ||
| This fetches the required GHC version and haskell dependencies from the default stackage snapshot (configured in `stack.yaml`), 
 | ||
| then builds all hledger packages.
 | ||
| This can take a while! To save time, you can build individual packages, eg just the CLI and TUI.
 | ||
| 
 | ||
| Note stack does not fetch C libraries such as curses or terminfo, which you might need to install yourself, using your system's package manager.
 | ||
| In case of trouble, see [download](/download.html#link-errors).
 | ||
| 
 | ||
| If you want to use an older snapshot/GHC for some reason, specify one of the older stack-ghc*.yaml files:
 | ||
| 
 | ||
|     stack --stack-yaml stack-ghc8.2.yaml build
 | ||
|     
 | ||
| ### Run in place
 | ||
| 
 | ||
|     stack exec -- hledger     # ARGS...
 | ||
|     stack exec -- hledger-ui  # ARGS...
 | ||
|     stack exec -- which hledger
 | ||
| 
 | ||
| ### Build and install
 | ||
| 
 | ||
| This builds and also copies the hledger executables to `~/.local/bin` or the Windows equivalent
 | ||
| (which you should  [add to your `$PATH`](/download.html#b)).
 | ||
| 
 | ||
|     stack install    # hledger hledger-ui ...
 | ||
| 
 | ||
| ### Run package tests
 | ||
| 
 | ||
| Runs any HUnit/doctest/easytest tests defined by each hledger package.
 | ||
| 
 | ||
|     stack test    # hledger ...
 | ||
| 
 | ||
| ### Run package benchmarks
 | ||
| 
 | ||
| Runs any performance reports defined by each hledger package.
 | ||
| 
 | ||
|     stack bench    # hledger ...
 | ||
| 
 | ||
| ### Run quickbench benchmarks
 | ||
| 
 | ||
| Times the end-user commands in `bench.sh` using quickbench.
 | ||
| 
 | ||
|     make bench
 | ||
| 
 | ||
| ### Run functional tests
 | ||
| 
 | ||
| Runs the shelltestrunner tests defined in tests/, which test the hledger CLI.
 | ||
| 
 | ||
|     stack build hledger
 | ||
|     make functest
 | ||
| 
 | ||
| ### Run haddock tests
 | ||
| 
 | ||
| Checks for anything that would break haddock doc generation.
 | ||
| 
 | ||
|     make haddocktest
 | ||
| 
 | ||
| Checks for the unit-tests embedded in documentation.
 | ||
| 
 | ||
|     make doctest
 | ||
| 
 | ||
| ### Simulate Travis tests
 | ||
| 
 | ||
| Locally runs tests similar to what we run on Travis CI.
 | ||
| 
 | ||
|     make travistest
 | ||
| 
 | ||
| ### Test with all supported GHC versions/stackage snapshots
 | ||
| 
 | ||
|     make allsnapshotstest
 | ||
| 
 | ||
| ### Use GHCI
 | ||
| 
 | ||
| GHCI is GHC's REPL, useful for exploring and calling code interactively.
 | ||
| 
 | ||
| #### Get a GHCI prompt for hledger-lib:
 | ||
| 
 | ||
|     cd hledger-lib; stack ghci hledger-lib
 | ||
| 
 | ||
| Changing into the package directory isn't actually needed, but it
 | ||
| enables a custom .ghci script which sets a more useful short prompt.
 | ||
| 
 | ||
| #### Get a GHCI prompt for hledger:
 | ||
| 
 | ||
|     cd hledger; stack ghci hledger
 | ||
| 
 | ||
| #### Get a GHCI prompt for hledger-ui:
 | ||
| 
 | ||
|     cd hledger-ui; stack ghci hledger-ui
 | ||
| 
 | ||
| #### Get a GHCI prompt for hledger-web:
 | ||
| 
 | ||
|     cd hledger-web; stack ghci hledger-web
 | ||
| 
 | ||
| hledger-web also needs to find some things in its current directory (like the static/ directory).
 | ||
| This normally just works, if not please [send details](https://github.com/simonmichael/hledger/issues/274).
 | ||
| 
 | ||
| ### Add a test
 | ||
| 
 | ||
| - identify what to test
 | ||
| - choose the test type: unit ? functional ? benchmark ?
 | ||
| - currently expected to pass or fail ?
 | ||
| - figure out where it goes
 | ||
| - write test, verify expected result
 | ||
| - get it committed
 | ||
| 
 | ||
| ### Fix a bug or add a feature
 | ||
| 
 | ||
| - research, discuss, validate the issue/feature on irc/list/bug tracker
 | ||
| - look for related tests, run the tests and check they are passing
 | ||
| - add a test ?
 | ||
| - develop a patch
 | ||
| - include any related issue numbers in the patch name, eg: "fix for blah blah (#NNN)"
 | ||
| - get it committed
 | ||
| 
 | ||
| ### Get your changes accepted
 | ||
| 
 | ||
| Follow the usual github workflow:
 | ||
| 
 | ||
| - fork the main hledger repo on github,
 | ||
| - git clone it to your local machine,
 | ||
| - git commit, after (?) pulling and merging the latest upstream changes
 | ||
| - git push back to github,
 | ||
| - open a pull request on github,
 | ||
| - follow up on any discussion there.
 | ||
| 
 | ||
| If you're new to this process, [help.github.com](http://help.github.com) may be useful.
 | ||
| 
 | ||
| ### Add yourself to the contributor list
 | ||
| 
 | ||
| - after getting something into the master branch, read and sign the [contributor list & agreement](contributors.html). Or, [ask](/docs.html#helpfeedback) to be added.
 | ||
| - give yourself a high five!
 | ||
| 
 | ||
| ### Work on docs
 | ||
| 
 | ||
| Most docs tasks are handled by [[Shake]]. 
 | ||
| 
 | ||
| #### List Shake rules:
 | ||
| 
 | ||
|     ./Shake
 | ||
| 
 | ||
| #### Generate man/info/txt manuals (in hledger*/) and embed in hledger executables:
 | ||
| 
 | ||
|     ./Shake manuals
 | ||
|     stack build
 | ||
| 
 | ||
| #### Generate html manuals and the hledger website (in site/_site/):
 | ||
| 
 | ||
|     ./Shake website
 | ||
| 
 | ||
| #### To remove all files generated by Shake:
 | ||
| 
 | ||
|     ./Shake Clean
 | ||
| 
 | ||
| ### Use ghcid for watching GHC/GHCI
 | ||
| 
 | ||
| [ghcid](http://hackage.haskell.org/package/ghcid) is the most reliable and fastest way to see GHC's feedback, and optionally run tests or a GHCI command, as you edit. We run it via make, for convenience and to watch multiple packages rather than just one. Run `make help-ghcid` to list related rules.
 | ||
| 
 | ||
| #### Watch for compile errors in hledger-lib and hledger:
 | ||
| 
 | ||
|     make ghcid
 | ||
| 
 | ||
| #### Watch compile errors and the output of some hledger command:
 | ||
| 
 | ||
|     ghcid -c 'make ghci' -T ':main -f a.j bal --budget -N'
 | ||
| 
 | ||
| ### Use --file-watch for watching stack
 | ||
| 
 | ||
| stack's --file-watch flag will re-run build/test/bench when source files or package.yaml/cabal files change. Eg:
 | ||
| 
 | ||
|     stack test hledger --file-watch
 | ||
| 
 | ||
| If you find that adding --fast makes this any faster, please update this.
 | ||
| 
 | ||
| ### Use entr for watching arbitrary commands
 | ||
| 
 | ||
| [entr](http://entrproject.org/) is the most robust cross-platform tool for watching files and running a command when they change. Note its first argument must be an executable program, to run a shell command or multiple commands use `bash -c "..."`.
 | ||
| 
 | ||
| #### Rerun a single functional test as you change it:
 | ||
| 
 | ||
|     ls tests/budget/budget.test | entr bash -c 'clear; COLUMNS=80 stack exec -- shelltest --execdir tests/budget/budget.test -i12'
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| ## Code
 | ||
| 
 | ||
| hledger is a suite of applications, tools and libraries.
 | ||
| The main hledger code repository is [github.com/simonmichael/hledger](http://github.com/simonmichael/hledger)
 | ||
| (shortcut url `code.hledger.org`).
 | ||
| There are also various hledger addons 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](http://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](http://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](http://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:Journal),
 | ||
| which contains a list of
 | ||
| [Transactions](http://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:Transaction),
 | ||
| each containing multiple
 | ||
| [Postings](http://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:Posting)
 | ||
| of some
 | ||
| [MixedAmount](http://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:MixedAmount)
 | ||
| (multiple
 | ||
| single-[CommoditySymbol](http://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:CommoditySymbol)
 | ||
| [Amounts](http://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:Amount))
 | ||
| to some
 | ||
| [AccountName](http://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:AccountName).
 | ||
| When needed, the Journal is further processed to derive a
 | ||
| [Ledger](http://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:Ledger),
 | ||
| which contains summed 
 | ||
| [Accounts](http://hackage.haskell.org/package/hledger-lib/docs/Hledger-Data-Types.html#t:Account).
 | ||
| In [Hledger.Reports](http://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="dev/data-model.png" class="highslide" onclick="return hs.expand(this)">
 | ||
| <img src="dev/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 -- Price
 | ||
| Amount -- AmountStyle
 | ||
| </uml>
 | ||
| -->
 | ||
| 
 | ||
| #### hledger
 | ||
| 
 | ||
| [package](http://hackage.haskell.org/package/hledger),
 | ||
| [code](https://github.com/simonmichael/hledger/tree/master/hledger),
 | ||
| [manual](http://hledger.org/manual.html#hledger)
 | ||
| 
 | ||
| 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](http://hackage.haskell.org/package/hledger-ui),
 | ||
| [code](https://github.com/simonmichael/hledger/tree/master/hledger-ui),
 | ||
| [manual](http://hledger.org/manual.html#hledger-ui)
 | ||
| 
 | ||
| A curses-style text interface.
 | ||
| 
 | ||
| #### hledger-web
 | ||
| 
 | ||
| [package](http://hackage.haskell.org/package/hledger-web),
 | ||
| [code](https://github.com/simonmichael/hledger/tree/master/hledger-web),
 | ||
| [manual](http://hledger.org/manual.html#hledger-web)
 | ||
| 
 | ||
| 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](http://hledger.org/manual.html#queries):
 | ||
| 
 | ||
| - [/journal](http://demo.hledger.org/journal), showing general journal entries (like `hledger print`)
 | ||
| 
 | ||
| - [/register](http://demo.hledger.org/register?q=inacct:Expenses:Food),
 | ||
|   showing transactions affecting an account (slightly different from
 | ||
|   hledger's [register](http://hledger.org/manual.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](http://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):
 | ||
| ```shell
 | ||
| $ (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.
 | ||
| ```shell
 | ||
| $ (cd hledger-web; stack ghci hledger-web)
 | ||
| hledger-web> :main --serve   # restart: ctrl-c, :r, enter, ctrl-p, ctrl-p, enter
 | ||
| ```
 | ||
| 
 | ||
| - `make 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:
 | ||
| ```shell
 | ||
| $ make 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.)
 | ||
| 
 | ||
| #### hledger-api
 | ||
| 
 | ||
| [package](http://hackage.haskell.org/package/hledger-api),
 | ||
| [code](https://github.com/simonmichael/hledger/tree/master/hledger-api),
 | ||
| [manual](http://hledger.org/manual.html#hledger-api)
 | ||
| 
 | ||
| A web API server. Uses the servant framework.
 | ||
| 
 | ||
| ### Quality
 | ||
| 
 | ||
| Relevant tools include:
 | ||
| 
 | ||
| - unit tests (HUnit, make unittest)
 | ||
| - functional tests (shelltestrunner, make functest)
 | ||
| - performance tests (simplebench, make bench)
 | ||
| - documentation tests (make haddocktest + manual)
 | ||
| - ui tests (manual)
 | ||
| - installation tests (manual)
 | ||
| - code reviews
 | ||
| 
 | ||
| ### Code review
 | ||
| 
 | ||
| - Code review party 2014/7/21-25:
 | ||
|   [discussion](http://thread.gmane.org/gmane.comp.finance.ledger.hledger/1070)<!-- missing ,
 | ||
|   [log](http://hledger.org/static/irc-20140725-code-review.html) -->
 | ||
| - Dev sprint/party 2015/10/10:
 | ||
|   [discussion](http://thread.gmane.org/gmane.comp.finance.ledger.hledger/1254)<!-- ircbrowse down ,
 | ||
|   [pre-chat](http://ircbrowse.net/day/hledger/2015/10/10),
 | ||
|   [log](http://ircbrowse.net/day/hledger/2015/10/11) -->
 | ||
| 
 | ||
| 
 | ||
| ## Pull requests
 | ||
| 
 | ||
| Most contributed hledger code (and some of the project maintainer's code)
 | ||
| is submitted and reviewed via Github pull requests.
 | ||
| Here are some tips for contributing PRs to hledger.
 | ||
| 
 | ||
| ### Code review is important
 | ||
| 
 | ||
| We aim to improve and sustain hledger's quality and maintainability over the long term.
 | ||
| 
 | ||
| Many PRs, especially small ones, and even some big ones, can be merged quickly. 
 | ||
| We love merging good PRs quickly.
 | ||
| 
 | ||
| Some bigger or more risky PRs can require substantial review, discussion, changes, or re-submission. 
 | ||
| Sometimes this is a bigger task than the coding.
 | ||
| Much valuable design, quality control, and knowledge sharing happens at this time. 
 | ||
| Some PRs get rejected, but their discussion and exploration can still be a useful contribution.
 | ||
| We very much want to avoid wasted work, but it occasionally happens. 
 | ||
| Our process is evolving and imperfect.
 | ||
| All of this is normal.
 | ||
| 
 | ||
| We hope you'll see it as a golden opportunity to collaborate with experts,
 | ||
| share and receive knowledge, refine your design/documentation/code,
 | ||
| and practice real-world development and communication skills.
 | ||
| Patience and persistence pays.
 | ||
| 
 | ||
| ### The pull request
 | ||
| 
 | ||
| A PR should have a clear purpose, documented in its description. Mention any #ISSUENOs addressed.
 | ||
| 
 | ||
| Don't tackle too much at once. 
 | ||
| Smaller/more focussed PRs can be reviewed quicker and accepted (or rejected) quicker.
 | ||
| 
 | ||
| Consider showing a draft of documentation first (more on this below).
 | ||
| 
 | ||
| ### The commit(s)
 | ||
| 
 | ||
| Commits should be easy to review.
 | ||
| Ideally each commit is complete, and has a single clear purpose,
 | ||
| which should be documented in the summary (and long description, if needed).
 | ||
| \#ISSUENOs can be mentioned in summary/description too when appropriate.
 | ||
| 
 | ||
| Within the above constraint, fewer, larger commits are preferred.
 | ||
| 
 | ||
| Keep in mind that commit messages are valuable documentation 
 | ||
| for future developers and troubleshooters. 
 | ||
| They are also the starting point for package changelogs and hledger release notes.
 | ||
| High-quality commit messages makes the release process quicker, and the resulting docs better. 
 | ||
| 
 | ||
| User-impacting commits should mention the user-visible changes, 
 | ||
| and be described in user-relevant language.
 | ||
| Library-user-impacting commits, eg API changes, ideally will also
 | ||
| be called out, and can described in more technical language.
 | ||
| Commits affecting hledger internals are less important, 
 | ||
| but you may notice some adhoc conventions if you browse the history.
 | ||
| In particular, you can optionally prefix the summary with short component codes (cf [[Issues]])
 | ||
| to facilitate history reading and changelog/release note production.
 | ||
| 
 | ||
| Rewrite and force-push your commits freely (rebase -i, push -f) to clean them up. 
 | ||
| Unless we decide to squash the PR into one commit, 
 | ||
| your commits will become part of hledger's history "for all time", 
 | ||
| so think about future developers trying to understand them, git bisect, etc.   
 | ||
| 
 | ||
| Rebase your commits against latest master for easiest review. Especially if they start to conflict.
 | ||
| 
 | ||
| ### The docs
 | ||
| 
 | ||
| PRs should include appropriate updates to reference documentation, unless otherwise agreed.
 | ||
| Typically this means the manual source files (hledger*/hledger*.m4.md).
 | ||
| It can also involve
 | ||
| command line option names and descriptions,
 | ||
| other --help output,
 | ||
| hledger's commands list,
 | ||
| hledger-ui's help dialog,
 | ||
| hledger-web's help dialog,
 | ||
| etc.
 | ||
| Sometimes it means the developer docs, at least the ones in the main repo (READMEs).
 | ||
| 
 | ||
| Reviewers can understand your PR more efficiently once proposed doc changes are provided, and may postpone it otherwise.
 | ||
| We are happy to help with the docs if needed - just ask.
 | ||
| 
 | ||
| Updating rendered manuals (hledger.{1,info,txt,md,html}) is not required, and probably best avoided to reduce conflicts.
 | ||
| Updating other docs such as tutorials, how-tos, examples, or screenshots is not required,
 | ||
| though it's welcome (may be in a different repo).
 | ||
| 
 | ||
| ### Documentation first
 | ||
| 
 | ||
| hledger follows documentation-driven design.
 | ||
| It is in fact highly effective, and highly recommended,
 | ||
| to write the new docs (help text/reference manual/haddocks/developer README..) before writing any code.
 | ||
| You can share a rough draft on IRC, on the mail list, in an issue comment,
 | ||
| or in a "WIP" PR starting with just the proposed docs commit.
 | ||
| 
 | ||
| This is often the quickest road to getting something merged into hledger.
 | ||
| hledger's many parts interact in surprisingly complex ways.
 | ||
| The documentation-driven working style lets us discuss, clarify and reach a good-enough consensus economically,
 | ||
| after which coding/review/acceptance can go quicker.
 | ||
| <!--
 | ||
| changes can impact past and future users,
 | ||
| ease of contribution,
 | ||
| long-term maintenance costs,
 | ||
| product architecture,
 | ||
| compatibility with the larger plain text accounting ecosystem,
 | ||
| etc.
 | ||
| -->
 | ||
| 
 | ||
| 
 | ||
| ## Tests
 | ||
| 
 | ||
| About testing in the hledger project, as of 201809.
 | ||
| 
 | ||
| ### Kinds of tests
 | ||
| 
 | ||
| 1.  Unit tests
 | ||
| 
 | ||
|     Unit tests exercise small chunks of functionality. In hledger, that
 | ||
|     means a function. So, many of our functions have one or more unit
 | ||
|     tests. These are mostly in hledger-lib, with a few in hledger.
 | ||
| 
 | ||
|     Our unit tests use
 | ||
|     [easytest](http://hackage.haskell.org/package/easytest) and some
 | ||
|     helpers from
 | ||
|     [Hledger.Utils.Test](https://github.com/simonmichael/hledger/blob/master/hledger-lib/Hledger/Utils/Test.hs),
 | ||
|     and follow a consistent pattern which aims to keep them
 | ||
| 
 | ||
|     -   easy to read (clear, concise)
 | ||
|     -   easy to write (low boilerplate, low cognitive load)
 | ||
|     -   easy to maintain (easy to edit, easy to refactor, robust)
 | ||
|     -   easy to associate with the code under test (easy to view/jump
 | ||
|         between code & test, easy to estimate coverage)
 | ||
|     -   and scalable (usable for all devs, easy to run and select,
 | ||
|         suitable for small/large modules/packages).
 | ||
| 
 | ||
|     Here\'s the pattern (let us know if you see a better way):
 | ||
| 
 | ||
|     ``` {.haskell}
 | ||
|     module Foo (
 | ||
|       ...
 | ||
|       tests_Foo -- export this module's and submodules' tests
 | ||
|     )
 | ||
|     where
 | ||
|     import Hledger  -- provides Hledger.Utils.Test helpers
 | ||
|     import Bar      -- submodules, providing tests_Bar etc.
 | ||
|     import Baz
 | ||
| 
 | ||
|     functionA = ...
 | ||
|     functionB = ...
 | ||
|     functionC = ...
 | ||
|     functionD = ...
 | ||
| 
 | ||
|     tests_Foo = tests "Foo" [ -- define tests at the end of each module
 | ||
| 
 | ||
|        tests "functionA" [
 | ||
|          test "a basic test"           $ expect SOMEBOOL
 | ||
|         ,test "a pretty equality test" $ SOMEEXPR `is` EXPECTEDVALUE
 | ||
|         ,test "a pretty parsing test"  $ expectParseEq PARSER INPUT EXPECTEDRESULT
 | ||
|         ,test "a sequential test" $ do
 | ||
|           A `is` B
 | ||
|           io $ doSomeIO
 | ||
|           C `is` D
 | ||
|         ,_test "an ignored test"  $ ...  -- will be skipped
 | ||
|         ]
 | ||
| 
 | ||
|       ,tests "functionB" [
 | ||
|          it "does blah"  $ ...  -- alternate spelling for test/_test
 | ||
|         ,_it "will bleh" $ ...
 | ||
|         ]
 | ||
| 
 | ||
|       ,tests "functionC" [
 | ||
|          expect BOOL            -- unnamed tests (harder to identify/select)
 | ||
|         ,EXPR `is` VALUE
 | ||
|         ]
 | ||
| 
 | ||
|       ,_tests "functionD" [     -- will be skipped
 | ||
|         ...
 | ||
|         ]
 | ||
| 
 | ||
|       ,tests_Foo            -- aggregate submodule tests
 | ||
|       ,tests_Bar
 | ||
|       ]
 | ||
|     ```
 | ||
| 
 | ||
|     Here are
 | ||
|     [some](https://github.com/simonmichael/hledger/blob/master/hledger-lib/Hledger/Data/Posting.hs#L296)
 | ||
|     real-world
 | ||
|     [examples](https://github.com/simonmichael/hledger/blob/master/hledger-lib/Hledger/Read/JournalReader.hs#L579).
 | ||
| 
 | ||
|     The unit tests are shipped as part of the hledger executable, and
 | ||
|     can always be run via the [test](http://hledger.org/manual#test)
 | ||
|     command (`hledger test`).
 | ||
| 
 | ||
|     Here\'s the quick way to run unit tests while developing:\
 | ||
|     `make ghcid-test` or `make ghcid-test-Hledger.Some.Module`.
 | ||
| 
 | ||
| 2.  Doc tests
 | ||
| 
 | ||
|     Like unit tests, but defined inside functions\' haddock
 | ||
|     documentation, in the style of a GHCI transcript. These test
 | ||
|     functionality, provide usage examples in the API docs, and test
 | ||
|     those examples, all at once. They are a bit more finicky and slower
 | ||
|     than unit tests. See
 | ||
|     [doctest](http://hackage.haskell.org/package/doctest) for more.
 | ||
| 
 | ||
|     doctests [do not work on Mac with GHC
 | ||
|     8.4+](https://github.com/sol/doctest/issues/199), out of the box.
 | ||
|     See
 | ||
|     [ghc\#15105](https://ghc.haskell.org/trac/ghc/ticket/15105#comment:10)
 | ||
|     for current status and a workaround.
 | ||
| 
 | ||
| 3.  Functional tests
 | ||
| 
 | ||
|     Functional tests test the overall functioning of the program. For
 | ||
|     hledger, that means running `hledger` with various inputs and
 | ||
|     options and checking for the expected output. This exercises
 | ||
|     functionality in the hledger and hledger-lib packages. We do this
 | ||
|     with
 | ||
|     [shelltestrunner](http://hackage.haskell.org/package/shelltestrunner).
 | ||
|     Tests are defined in files under
 | ||
|     [tests/](https://github.com/simonmichael/hledger/tree/master/tests),
 | ||
|     grouped by *component* (command or topic name).
 | ||
| 
 | ||
| 4.  Code tests
 | ||
| 
 | ||
|     We have some tests aimed at testing eg code quality, generally
 | ||
|     defined as make rules, such as:
 | ||
| 
 | ||
|       --------------------- -------------------------------------------------------------------------------------
 | ||
|       `make haddocktest`    can haddock process all code docs without error
 | ||
|       `make buildtest`      does all code build warning free with the default GHC version & stackage snapshot
 | ||
|       `make buildtestall`   does the code build warning free with all supported GHC versions/stackage snapshots
 | ||
|       --------------------- -------------------------------------------------------------------------------------
 | ||
| 
 | ||
|     See below for examples.
 | ||
| 
 | ||
| 5.  Package test suites
 | ||
| 
 | ||
|     Haskell tools like stack and cabal recognise test suites defined in
 | ||
|     a package\'s cabal file (or package.yaml file). These can be run via
 | ||
|     `stack test`, `cabal test` etc., and they are required to build and
 | ||
|     pass by services like Stackage. Here are the currently hledger
 | ||
|     package test suites:
 | ||
| 
 | ||
|       ------------- ------------ ---------------------------------------------------------------
 | ||
|       package       test suite   what it runs
 | ||
|       hledger-lib   doctests     doctests
 | ||
|       hledger-lib   easytests    unit tests
 | ||
|       hledger       test         builtin test command (hledger\'s + hledger-lib\'s unit tests)
 | ||
|       hledger-ui                 
 | ||
|       hledger-web                
 | ||
|       hledger-api                
 | ||
|       ------------- ------------ ---------------------------------------------------------------
 | ||
| 
 | ||
| ### Coverage
 | ||
| 
 | ||
| This means how thoroughly the code is tested - both in breadth (are all
 | ||
| parts of the code tested at least a little ?) and in depth (are all
 | ||
| possible code paths, states, situations tested ?).
 | ||
| 
 | ||
| Our current test coverage can be summarised like so:
 | ||
| 
 | ||
|   ------------- ------ ----- ------------
 | ||
|   package       unit   doc   functional
 | ||
|   hledger-lib   X      X     X
 | ||
|   hledger       X            X
 | ||
|   hledger-ui                 
 | ||
|   hledger-web                
 | ||
|   hledger-api                
 | ||
|   ------------- ------ ----- ------------
 | ||
| 
 | ||
| There are ways to generate detailed coverage reports for haskell unit
 | ||
| tests, at least. It would be useful to set this up for hledger.
 | ||
| 
 | ||
| ### How to run tests
 | ||
| 
 | ||
| Run unit tests:
 | ||
| 
 | ||
| ``` {.example}
 | ||
| $ make unittest
 | ||
| ```
 | ||
| 
 | ||
| Run doctests:
 | ||
| 
 | ||
| ``` {.example}
 | ||
| $ make doctest
 | ||
| ```
 | ||
| 
 | ||
| Run functional tests (and unit tests, now):
 | ||
| 
 | ||
| ``` {.example}
 | ||
| $ stack install shelltestrunner
 | ||
| $ make functest
 | ||
| ```
 | ||
| 
 | ||
| Run the package tests (unit tests, maybe doctests, but not functional
 | ||
| tests) of all or selected packages.
 | ||
| 
 | ||
| ``` {.example}
 | ||
| $ stack test [PKG]
 | ||
| ```
 | ||
| 
 | ||
| Run \"default tests: package plus functional tests\":
 | ||
| 
 | ||
| ``` {.example}
 | ||
| $ make test
 | ||
| ```
 | ||
| 
 | ||
| Test generation of haddock docs:
 | ||
| 
 | ||
| ``` {.example}
 | ||
| $ make haddocktest
 | ||
| ```
 | ||
| 
 | ||
| Thorough test for build issues with current GHC:
 | ||
| 
 | ||
| ``` {.example}
 | ||
| $ make buildtest
 | ||
| ```
 | ||
| 
 | ||
| Thorough test for build issues with all supported GHC versions:
 | ||
| 
 | ||
| ``` {.example}
 | ||
| $ make buildtestall
 | ||
| ```
 | ||
| 
 | ||
| Run built-in hledger/hledger-lib unit tests via hledger command:
 | ||
| 
 | ||
| ``` {.example}
 | ||
| $ hledger test  # test installed hledger
 | ||
| $ stack build hledger && stack exec -- hledger test  # test just-built hledger
 | ||
| $ hledger test --help
 | ||
| test [TESTPATTERN] [SEED]
 | ||
|   Run the unit tests built in to hledger-lib and hledger,
 | ||
|   printing results on stdout and exiting with success or failure.
 | ||
|   Tests are run in two batches: easytest-based and hunit-based tests.
 | ||
|   If any test fails or gives an error, the exit code will be non-zero.
 | ||
|   If a pattern argument (case sensitive) is provided, only easytests
 | ||
|   in that scope and only hunit tests whose name contains it are run.
 | ||
|   If a numeric second argument is provided, it will set the randomness
 | ||
|   seed for easytests.
 | ||
| ```
 | ||
| 
 | ||
| Rebuild and rerun hledger/hledger-lib unit tests via ghcid:
 | ||
| 
 | ||
| ``` {.example}
 | ||
| $ make ghcid-test
 | ||
| ```
 | ||
| 
 | ||
| Rebuild and rerun only some tests via ghcid (see hledger test --help):
 | ||
| 
 | ||
| ``` {.example}
 | ||
| $ make ghcid-test-TESTPATTERN
 | ||
| ```
 | ||
| 
 | ||
| See all test-related make rules:
 | ||
| 
 | ||
| ``` {.example}
 | ||
| $ make help-test
 | ||
| ```
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| ## Benchmarks
 | ||
| 
 | ||
| Benchmarks are standard performance measurements,
 | ||
| which we define using `bench` declarations in cabal files.
 | ||
| There is [one in hledger.cabal](https://github.com/simonmichael/hledger/blob/master/hledger/hledger.cabal#L228),
 | ||
| with related code and data files in [hledger/bench/](https://github.com/simonmichael/hledger/tree/master/hledger/bench).
 | ||
| 
 | ||
| To run the standard hledger benchmark, use `stack bench hledger`.
 | ||
| This installs haskell dependencies (but not system dependencies) and rebuilds as needed,
 | ||
| then runs [hledger/bench/bench.hs](https://github.com/simonmichael/hledger/blob/master/hledger/bench/bench.hs),
 | ||
| which by default shows quick elapsed-time measurements for several operations on a standard data file:
 | ||
| 
 | ||
| ```shell
 | ||
| $ stack bench hledger
 | ||
| NOTE: the bench command is functionally equivalent to 'build --bench'
 | ||
| ...
 | ||
| hledger-0.27: benchmarks
 | ||
| Running 1 benchmarks...
 | ||
| Benchmark bench: RUNNING...
 | ||
| Benchmarking hledger in /Users/simon/src/hledger/hledger with timeit
 | ||
| read bench/10000x1000x10.journal        [1.57s]
 | ||
| print                                   [1.29s]
 | ||
| register                                [1.92s]
 | ||
| balance                                 [0.21s]
 | ||
| stats                                   [0.23s]
 | ||
| Total: 5.22s
 | ||
| Benchmark bench: FINISH
 | ||
| ```
 | ||
| 
 | ||
| bench.hs has some other modes, which you can use by compiling and running it directly.
 | ||
| `--criterion` reports more detailed and dependable measurements, but takes longer:
 | ||
| 
 | ||
| ```shell
 | ||
| $ cd hledger; stack exec -- ghc -ibench bench/bench && bench/bench --criterion
 | ||
| ...
 | ||
| Linking bench/bench ...
 | ||
| Benchmarking hledger in /Users/simon/src/hledger/hledger with criterion
 | ||
| benchmarking read bench/10000x1000x10.journal
 | ||
| time                 1.414 s    (1.234 s .. 1.674 s)
 | ||
|                      0.996 R²   (0.989 R² .. 1.000 R²)
 | ||
| mean                 1.461 s    (1.422 s .. 1.497 s)
 | ||
| std dev              59.69 ms   (0.0 s .. 62.16 ms)
 | ||
| variance introduced by outliers: 19% (moderately inflated)
 | ||
| 
 | ||
| benchmarking print
 | ||
| time                 1.323 s    (1.279 s .. 1.385 s)
 | ||
|                      1.000 R²   (0.999 R² .. 1.000 R²)
 | ||
| mean                 1.305 s    (1.285 s .. 1.316 s)
 | ||
| std dev              17.20 ms   (0.0 s .. 19.14 ms)
 | ||
| variance introduced by outliers: 19% (moderately inflated)
 | ||
| 
 | ||
| benchmarking register
 | ||
| time                 1.995 s    (1.883 s .. 2.146 s)
 | ||
|                      0.999 R²   (0.998 R² .. NaN R²)
 | ||
| mean                 1.978 s    (1.951 s .. 1.995 s)
 | ||
| std dev              25.09 ms   (0.0 s .. 28.26 ms)
 | ||
| variance introduced by outliers: 19% (moderately inflated)
 | ||
| 
 | ||
| benchmarking balance
 | ||
| time                 251.3 ms   (237.6 ms .. 272.4 ms)
 | ||
|                      0.998 R²   (0.997 R² .. 1.000 R²)
 | ||
| mean                 260.4 ms   (254.3 ms .. 266.5 ms)
 | ||
| std dev              7.609 ms   (3.192 ms .. 9.638 ms)
 | ||
| variance introduced by outliers: 16% (moderately inflated)
 | ||
| 
 | ||
| benchmarking stats
 | ||
| time                 325.5 ms   (299.1 ms .. 347.2 ms)
 | ||
|                      0.997 R²   (0.985 R² .. 1.000 R²)
 | ||
| mean                 329.2 ms   (321.5 ms .. 339.6 ms)
 | ||
| std dev              11.08 ms   (2.646 ms .. 14.82 ms)
 | ||
| variance introduced by outliers: 16% (moderately inflated)
 | ||
| ```
 | ||
| 
 | ||
| `--simplebench` shows a table of elapsed-time measurements for the commands defined in [bench/default.bench](https://github.com/simonmichael/hledger/blob/master/hledger/bench/default.bench).
 | ||
| It can also show the results for multiple h/ledger executables side by side, if you tweak the bench.hs code.
 | ||
| Unlike the other modes, it does not link with the hledger code directly, but runs the "hledger" executable found in $PATH (so ensure that's the one you intend to test).
 | ||
| 
 | ||
| ```shell
 | ||
| $ cd hledger; stack exec -- ghc -ibench bench/bench && bench/bench --simplebench
 | ||
| Benchmarking /Users/simon/.local/bin/hledger in /Users/simon/src/hledger/hledger with simplebench and shell
 | ||
| Using bench/default.bench
 | ||
| Running 4 tests 1 times with 1 executables at 2015-08-23 16:58:59.128112 UTC:
 | ||
| 1: hledger -f bench/10000x1000x10.journal print	[3.27s]
 | ||
| 1: hledger -f bench/10000x1000x10.journal register	[3.65s]
 | ||
| 1: hledger -f bench/10000x1000x10.journal balance	[2.06s]
 | ||
| 1: hledger -f bench/10000x1000x10.journal stats	[2.13s]
 | ||
| 
 | ||
| Summary (best iteration):
 | ||
| 
 | ||
| +-----------------------------------------++---------+
 | ||
| |                                         || hledger |
 | ||
| +=========================================++=========+
 | ||
| | -f bench/10000x1000x10.journal print    ||    3.27 |
 | ||
| | -f bench/10000x1000x10.journal register ||    3.65 |
 | ||
| | -f bench/10000x1000x10.journal balance  ||    2.06 |
 | ||
| | -f bench/10000x1000x10.journal stats    ||    2.13 |
 | ||
| +-----------------------------------------++---------+
 | ||
| ```
 | ||
| 
 | ||
| bench's --simplebench mode is based on a standalone tool, [tools/simplebench.hs](https://github.com/simonmichael/hledger/blob/master/tools/simplebench.hs).
 | ||
| simplebench.hs is a generic benchmarker of one or more executables (specified on the command line) against one or more sets of command-line arguments (specified in a file).
 | ||
| It has a better command-line interface than bench.hs, so you may find it more convenient
 | ||
| for comparing multiple hledger versions, or hledger and ledger. Eg:
 | ||
| 
 | ||
| ```shell
 | ||
| $ stack exec -- ghc tools/simplebench
 | ||
| [1 of 1] Compiling Main             ( tools/simplebench.hs, tools/simplebench.o )
 | ||
| Linking tools/simplebench ...
 | ||
| ```
 | ||
| ```shell
 | ||
| $ tools/simplebench -h
 | ||
| tools/simplebench -h
 | ||
| simplebench: at least one executable needed
 | ||
| bench [-f testsfile] [-n iterations] [-p precision] executable1 [executable2 ...]
 | ||
| 
 | ||
| Run some functional tests with each of the specified executables,
 | ||
| where a test is "zero or more arguments supported by all executables",
 | ||
| and report the best execution times.
 | ||
| 
 | ||
|   -f testsfile   --testsfile=testsfile    file containing tests, one per line, default: bench.tests
 | ||
|   -n iterations  --iterations=iterations  number of test iterations to run, default: 2
 | ||
|   -p precision   --precision=precision    show times with this precision, default: 2
 | ||
|   -v             --verbose                show intermediate results
 | ||
|   -h             --help                   show this help
 | ||
| 
 | ||
| Tips:
 | ||
| - executables may have arguments if enclosed in quotes
 | ||
| - tests can be commented out with #
 | ||
| - results are saved in benchresults.{html,txt}
 | ||
| ```
 | ||
| ```shell
 | ||
| cd hledger; $ ../tools/simplebench -f bench/default.bench hledger ledger
 | ||
| Using bench/default.bench
 | ||
| Running 4 tests 2 times with 2 executables at 2015-08-24 04:24:37.257068 UTC:
 | ||
| 
 | ||
| Summary (best iteration):
 | ||
| 
 | ||
| +-----------------------------------------++---------+--------+
 | ||
| |                                         || hledger | ledger |
 | ||
| +=========================================++=========+========+
 | ||
| | -f bench/10000x1000x10.journal print    ||    3.24 |   0.43 |
 | ||
| | -f bench/10000x1000x10.journal register ||    3.80 |   3.48 |
 | ||
| | -f bench/10000x1000x10.journal balance  ||    2.05 |   0.18 |
 | ||
| | -f bench/10000x1000x10.journal stats    ||    2.10 |   0.19 |
 | ||
| +-----------------------------------------++---------+--------+
 | ||
| ```
 | ||
| 
 | ||
| Finally, for quick, fine-grained performance measurements when troubleshooting or optimising, I use
 | ||
| [dev.hs](https://github.com/simonmichael/hledger/blob/master/dev.hs).
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| ## Version numbers
 | ||
| 
 | ||
| Some places version numbers appear:
 | ||
| 
 | ||
| - --version (and sometimes --help) output of all hledger* executables
 | ||
| - web manuals on hledger.org
 | ||
| - download page
 | ||
| - changelogs
 | ||
| - release notes
 | ||
| - release announcements
 | ||
| - hackage/stackage uris
 | ||
| - cabal tarball filenames
 | ||
| - platform-specific packages
 | ||
| 
 | ||
| Some old version numbering goals:
 | ||
| 
 | ||
| 1. automation, robustness, simplicity, platform independence
 | ||
| 2. cabal versions must be all-numeric
 | ||
| 3. release versions can be concise (without extra .0's)
 | ||
| 4. releases should have a corresponding VCS tag
 | ||
| 5. development builds should have a precise version appearing in --version
 | ||
| 6. development builds should generate cabal packages with non-confusing versions
 | ||
| 7. there should be a way to mark builds/releases as alpha or beta
 | ||
| 8. avoid unnecessary compiling and linking
 | ||
| 9. minimise VCS noise and syncing issues (commits, unrecorded changes)
 | ||
| 
 | ||
| Current version numbering policy:
 | ||
| 
 | ||
| - We (should) follow <http://haskell.org/haskellwiki/Package_versioning_policy>
 | ||
| 
 | ||
| - The "full release version" is ma.jor.minor, where minor is 0 for a
 | ||
|   normal release or 1..n for bugfix releases. Each component is a
 | ||
|   natural number (can be >= 10). Eg: 1.13 major release, 1.13.1
 | ||
|   bugfix release.
 | ||
| 
 | ||
| - The "release version", which we prefer to use when possible, is
 | ||
|   just ma.jor when minor is 0. Ie elide the dot zero.
 | ||
| 
 | ||
| - The build version is ma.jor.minor+patches, where patches is the number
 | ||
|   of patches applied in the current repo since the last release tag.
 | ||
| 
 | ||
| - `hledger --version` shows the release version or build version as
 | ||
|   appropriate.
 | ||
| 
 | ||
| - Release tags in the VCS are like PKG-VERSION. Eg hledger-1.13,
 | ||
| - hledger-ui-1.13.1.
 | ||
| 
 | ||
| Current process:
 | ||
| 
 | ||
| - In each hledger package directory there's a `.version` file
 | ||
|   containing its desired version number.
 | ||
|   
 | ||
| - After changing a `.version` file: run `./Shake setversion` to
 | ||
|   propagate the versions to all other places in the packages where
 | ||
|   they should appear. This is not perfect (see Shake.hs) so review and
 | ||
|   manually adjust the proposed changes before committing.  Those
 | ||
|   places include (you can also run these rules individually):
 | ||
| 
 | ||
|   - `PKG/package.yaml` contains the cabal package version declaration,
 | ||
|     bounds on other hledger packages, and a CPP VERSION macro used in
 | ||
|     `hledger/Hledger/Cli/Version.hs`. Changes in package.yaml will be
 | ||
|     propagated to `PKG/PKG.cabal` on the next stack or Shake build, or
 | ||
|     by `make gencabal`.
 | ||
| 
 | ||
|   - `PKG/defs.m4` contains the _version_ macro used in documentation
 | ||
|     source files (*.m4.md).
 | ||
| 
 | ||
| - At release time:
 | ||
| 
 | ||
|   - `./Shake PKG/CHANGES.md-finalise` converts the topmost heading, if
 | ||
|     it is an interim heading (just a commit hash), to a permanent
 | ||
|     heading containing the version and today's date.
 | ||
| 
 | ||
|   - for each package being released, a PKG-VERSION git tag is created.
 | ||
| 
 | ||
| - At major release time:
 | ||
| 
 | ||
|   - A new snapshot of the reference docs is added to the website, by
 | ||
|     `./Shake site/doc/VERSION/.snapshot`, and added to the links in
 | ||
|     `site/js/site.js`.
 | ||
| 
 | ||
| ## Sample journals
 | ||
| 
 | ||
| Synthetic data files like `examples/100x100x10.journal` are useful for benchmarks and testing.
 | ||
| The numbers describe the number of transactions, number of accounts, and maximum account depth respectively.
 | ||
| They are generated by [`tools/generatejournal.hs`](https://github.com/simonmichael/hledger/blob/master/tools/generatejournal.hs).
 | ||
| They should get built automatically as needed, if not you can use `make samplejournals`:
 | ||
| 
 | ||
| ```shell
 | ||
| $ make samplejournals
 | ||
| ghc tools/generatejournal.hs
 | ||
| [1 of 1] Compiling Main             ( tools/generatejournal.hs, tools/generatejournal.o )
 | ||
| Linking tools/generatejournal ...
 | ||
| tools/generatejournal 100 100 10 >examples/100x100x10.journal
 | ||
| tools/generatejournal 1000 1000 10 >examples/1000x1000x10.journal
 | ||
| tools/generatejournal 1000 10000 10 >examples/1000x10000x10.journal
 | ||
| tools/generatejournal 10000 1000 10 >examples/10000x1000x10.journal
 | ||
| tools/generatejournal 10000 10000 10 >examples/10000x10000x10.journal
 | ||
| tools/generatejournal 100000 1000 10 >examples/100000x1000x10.journal
 | ||
| tools/generatejournal 3 5 5 >examples/ascii.journal
 | ||
| tools/generatejournal 3 5 5 --chinese >examples/chinese.journal
 | ||
| tools/generatejournal 3 5 5 --mixed >examples/mixed.journal
 | ||
| ```
 | ||
| 
 | ||
| 
 | ||
| ## Docs
 | ||
| 
 | ||
| https://github.com/simonmichael/hledger/tree/master/doc
 | ||
| 
 | ||
| Project documentation lives in a number of places:
 | ||
| 
 | ||
| - `site/*.md` is the hledger.org website content, which is generated with hakyll[-std] and pandoc
 | ||
| - haddock documentation in the code appears on Hackage
 | ||
| - short blurbs: cabal files, module headers, HCAR, GSOC project, ..
 | ||
| - `doc/notes.org` has some old developer notes
 | ||
| - developer reports (profiles, benchmarks, coverage..) in doc/profs, sometimes published at hledger.org/profs
 | ||
| 
 | ||
| How to prepare changelogs & release notes
 | ||
| 
 | ||
| Changelogs:
 | ||
| 
 | ||
| - ./Shake changelogs
 | ||
| - edit the new changelog items (identify, filter, move to correct changelog, deduplicate, rewrite, sort/group)
 | ||
| 
 | ||
| Release notes:
 | ||
| 
 | ||
| - add a new TOC entry and section in site/release-notes.md
 | ||
| - copy/rewrite/summarise package changelogs 
 | ||
| - note any other items of interest
 | ||
| - list release contributors
 | ||
| - write release summary
 | ||
| 
 | ||
| 
 | ||
| ## Issues
 | ||
| 
 | ||
| The hledger project\'s issue tracker is on github. It contains:
 | ||
| 
 | ||
| -   BUG issues - failures in some part of the hledger project (the main
 | ||
|     hledger packages, docs, website..)
 | ||
| -   WISH issues - feature proposals, enhancement requests
 | ||
| -   uncategorised issues - we don\'t know what these are yet
 | ||
| -   pull requests - proposed changes to code and docs
 | ||
| 
 | ||
| ### Issue Urls
 | ||
| 
 | ||
| -   <http://bugs.hledger.org> - show open BUG issues
 | ||
| -   <http://wishes.hledger.org> - show open WISH issues
 | ||
| -   <http://issues.hledger.org> - show all issues, open or closed
 | ||
| -   <http://prs.hledger.org> - show open pull requests
 | ||
| -   <http://bugs.hledger.org/new> - create a new issue
 | ||
| 
 | ||
| ### Labels
 | ||
| 
 | ||
| Labels are used to categorise:
 | ||
| 
 | ||
| -   the issue\'s type: \"A BUG\" or \"A WISH\", in shades of red (The A
 | ||
|     makes it appear as first label)
 | ||
| -   relevant subsystems/topics, in light blue. More about this below.
 | ||
| -   relevant platforms, in light purple
 | ||
| -   resolution if not fixed:
 | ||
|     \"closed:cant-reproduce/duplicate/invalid/wont-fix\", in dark grey
 | ||
| -   \"bounty\", in bright yellow: issues with bountysource funding
 | ||
| -   \"easy?\", in dim yellow: issues which are probably relatively easy
 | ||
|     to fix
 | ||
| -   \"imported\" etc., in white: miscellaneous information
 | ||
| 
 | ||
| ### Components
 | ||
| 
 | ||
| Issues and the hledger project generally are organised into components:
 | ||
| mostly non-overlapping topics, one for each user command, add-on tool,
 | ||
| input format, output format, etc. Each component gets a light blue label
 | ||
| in the issue tracker. Component names (sometimes abbreviated) are used
 | ||
| as a prefix to commit messages, and to organise changelogs and release
 | ||
| notes. Below are the current components, and their open issues.
 | ||
| 
 | ||
| ### Custodians
 | ||
| 
 | ||
| If you are interested in helping with a particular component for a
 | ||
| while, please add yourself as a custodian in the issues table below. A
 | ||
| custodian\'s job is to help manage the issues, rally the troops, and
 | ||
| drive the open issue count towards zero. The more custodians, the
 | ||
| better! By dividing up the work this way, we can scale and make forward
 | ||
| progress.
 | ||
| 
 | ||
| ### Open issues
 | ||
| 
 | ||
| <!-- XXX This table was created in org mode and isn't really manageable in markdown. A reason to switch this doc to org. -->
 | ||
| 
 | ||
|   --------------------------------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------ ----------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------
 | ||
|   COMPONENT (DESCRIPTION, CUSTODIANS)                                                                                         BUGS (starter=good first issue)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           WISHES                                                                                                                   PRS                                                                                             ?
 | ||
|                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
 | ||
|   [all](https://github.com/simonmichael/hledger/issues?q=is:open) ([@simonmichael](https://github.com/simonmichael/))         [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?))                                                                                                   [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22)                           [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr)                           [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22)
 | ||
|   [install](https://github.com/simonmichael/hledger/issues?q=is:open+label:install) (hledger-install script)                  [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:install) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:install)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:install)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:install))                                           [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:install)             [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:install)             [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:install)
 | ||
|   [cli](https://github.com/simonmichael/hledger/issues?q=is:open+label:cli) (hledger tool)                                    [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:cli) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:cli)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:cli)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:cli))                                                           [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:cli)                 [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:cli)                 [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:cli)
 | ||
|   [ui](https://github.com/simonmichael/hledger/issues?q=is:open+label:ui) (hledger-ui tool)                                   [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:ui) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:ui)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:ui)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:ui))                                                               [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:ui)                  [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:ui)                  [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:ui)
 | ||
|   [web](https://github.com/simonmichael/hledger/issues?q=is:open+label:web) (hledger-web tool)                                [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:web) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:web)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:web)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:web))                                                           [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:web)                 [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:web)                 [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:web)
 | ||
|   [api](https://github.com/simonmichael/hledger/issues?q=is:open+label:api) (hledger-api tool)                                [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:api) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:api)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:api)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:api))                                                           [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:api)                 [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:api)                 [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:api)
 | ||
|   [interest](https://github.com/simonmichael/hledger/issues?q=is:open+label:interest) (hledger-interest tool)                 [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:interest) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:interest)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:interest)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:interest))                                       [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:interest)            [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:interest)            [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:interest)
 | ||
|   [journal](https://github.com/simonmichael/hledger/issues?q=is:open+label:journal) (journal format)                          [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:journal) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:journal)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:journal)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:journal))                                           [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:journal)             [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:journal)             [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:journal)
 | ||
|   [timeclock](https://github.com/simonmichael/hledger/issues?q=is:open+label:timeclock) (timeclock format)                    [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:timeclock) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:timeclock)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:timeclock)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:timeclock))                                   [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:timeclock)           [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:timeclock)           [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:timeclock)
 | ||
|   [timedot](https://github.com/simonmichael/hledger/issues?q=is:open+label:timedot) (timedot format)                          [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:timedot) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:timedot)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:timedot)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:timedot))                                           [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:timedot)             [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:timedot)             [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:timedot)
 | ||
|   [csv](https://github.com/simonmichael/hledger/issues?q=is:open+label:csv) (CSV format, CSV output)                          [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:csv) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:csv)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:csv)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:csv))                                                           [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:csv)                 [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:csv)                 [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:csv)
 | ||
|   [json](https://github.com/simonmichael/hledger/issues?q=is:open+label:json) (JSON output)                                   [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:json) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:json)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:json)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:json))                                                       [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:json)                [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:json)                [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:json)
 | ||
|   [html](https://github.com/simonmichael/hledger/issues?q=is:open+label:html) (HTML output)                                   [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:html) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:html)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:html)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:html))                                                       [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:html)                [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:html)                [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:html)
 | ||
|   [accounts](https://github.com/simonmichael/hledger/issues?q=is:open+label:accounts) (command)                               [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:accounts) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:accounts)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:accounts)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:accounts))                                       [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:accounts)            [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:accounts)            [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:accounts)
 | ||
|   [activity](https://github.com/simonmichael/hledger/issues?q=is:open+label:activity) (command)                               [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:activity) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:activity)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:activity)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:activity))                                       [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:activity)            [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:activity)            [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:activity)
 | ||
|   [add](https://github.com/simonmichael/hledger/issues?q=is:open+label:add) (command)                                         [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:add) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:add)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:add)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:add))                                                           [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:add)                 [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:add)                 [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:add)
 | ||
|   [balance](https://github.com/simonmichael/hledger/issues?q=is:open+label:balance), bal (command)                            [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:balance) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:balance)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:balance)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:balance))                                           [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:balance)             [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:balance)             [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:balance)
 | ||
|   [balancesheet](https://github.com/simonmichael/hledger/issues?q=is:open+label:balancesheet), bs (command)                   [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:balancesheet) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:balancesheet)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:balancesheet)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:balancesheet))                       [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:balancesheet)        [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:balancesheet)        [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:balancesheet)
 | ||
|   [cashflow](https://github.com/simonmichael/hledger/issues?q=is:open+label:cashflow), cf (command)                           [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:cashflow) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:cashflow)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:cashflow)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:cashflow))                                       [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:cashflow)            [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:cashflow)            [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:cashflow)
 | ||
|   [checkdates](https://github.com/simonmichael/hledger/issues?q=is:open+label:checkdates) (command)                           [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:checkdates) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:checkdates)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:checkdates)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:checkdates))                               [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:checkdates)          [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:checkdates)          [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:checkdates)
 | ||
|   [checkdupes](https://github.com/simonmichael/hledger/issues?q=is:open+label:checkdupes) (command)                           [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:checkdupes) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:checkdupes)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:checkdupes)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:checkdupes))                               [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:checkdupes)          [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:checkdupes)          [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:checkdupes)
 | ||
|   [close](https://github.com/simonmichael/hledger/issues?q=is:open+label:close) (command)                                     [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:close) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:close)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:close)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:close))                                                   [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:close)               [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:close)               [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:close)
 | ||
|   [import](https://github.com/simonmichael/hledger/issues?q=is:open+label:import) (command)                                   [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:import) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:import)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:import)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:import))                                               [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:import)              [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:import)              [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:import)
 | ||
|   [incomestatement](https://github.com/simonmichael/hledger/issues?q=is:open+label:incomestatement), is (command)             [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:incomestatement) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:incomestatement)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:incomestatement)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:incomestatement))           [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:incomestatement)     [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:incomestatement)     [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:incomestatement)
 | ||
|   [prices](https://github.com/simonmichael/hledger/issues?q=is:open+label:prices) (command)                                   [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:prices) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:prices)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:prices)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:prices))                                               [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:prices)              [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:prices)              [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:prices)
 | ||
|   [print](https://github.com/simonmichael/hledger/issues?q=is:open+label:print) (command)                                     [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:print) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:print)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:print)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:print))                                                   [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:print)               [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:print)               [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:print)
 | ||
|   [printunique](https://github.com/simonmichael/hledger/issues?q=is:open+label:printunique) (command)                         [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:printunique) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:printunique)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:printunique)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:printunique))                           [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:printunique)         [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:printunique)         [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:printunique)
 | ||
|   [register](https://github.com/simonmichael/hledger/issues?q=is:open+label:register), reg (command)                          [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:register) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:register)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:register)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:register))                                       [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:register)            [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:register)            [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:register)
 | ||
|   [registermatch](https://github.com/simonmichael/hledger/issues?q=is:open+label:registermatch) (command)                     [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:registermatch) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:registermatch)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:registermatch)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:registermatch))                   [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:registermatch)       [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:registermatch)       [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:registermatch)
 | ||
|   [rewrite](https://github.com/simonmichael/hledger/issues?q=is:open+label:rewrite) (command)                                 [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:rewrite) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:rewrite)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:rewrite)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:rewrite))                                           [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:rewrite)             [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:rewrite)             [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:rewrite)
 | ||
|   [roi](https://github.com/simonmichael/hledger/issues?q=is:open+label:roi) (command)                                         [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:roi) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:roi)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:roi)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:roi))                                                           [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:roi)                 [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:roi)                 [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:roi)
 | ||
|   [stats](https://github.com/simonmichael/hledger/issues?q=is:open+label:stats) (command)                                     [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:stats) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:stats)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:stats)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:stats))                                                   [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:stats)               [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:stats)               [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:stats)
 | ||
|   [tags](https://github.com/simonmichael/hledger/issues?q=is:open+label:tags) (command)                                       [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:tags) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:tags)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:tags)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:tags))                                                       [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:tags)                [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:tags)                [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:tags)
 | ||
|   [balcmds](https://github.com/simonmichael/hledger/issues?q=is:open+label:balcmds) (bal/bs/bse/cf/is)                        [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:balcmds) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:balcmds)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:balcmds)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:balcmds))                                           [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:balcmds)             [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:balcmds)             [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:balcmds)
 | ||
|   [budget](https://github.com/simonmichael/hledger/issues?q=is:open+label:budget) (balance --budget, budgeting)               [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:budget) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:budget)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:budget)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:budget))                                               [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:budget)              [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:budget)              [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:budget)
 | ||
|   [periodexpressions](https://github.com/simonmichael/hledger/issues?q=is:open+label:periodexpressions) (-b, -e, -p, date:)   [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:periodexpressions) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:periodexpressions)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:periodexpressions)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:periodexpressions))   [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:periodexpressions)   [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:periodexpressions)   [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:periodexpressions)
 | ||
|   [tags](https://github.com/simonmichael/hledger/issues?q=is:open+label:tags) (using tags)                                    [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:tags) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:tags)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:tags)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:tags))                                                       [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:tags)                [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:tags)                [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:tags)
 | ||
|   [doc](https://github.com/simonmichael/hledger/issues?q=is:open+label:doc) (documentation, help)                             [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:doc) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:doc)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:doc)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:doc))                                                           [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:doc)                 [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:doc)                 [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:doc)
 | ||
|   [site](https://github.com/simonmichael/hledger/issues?q=is:open+label:site) (website, web presence)                         [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:site) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:site)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:site)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:site))                                                       [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:site)                [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:site)                [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:site)
 | ||
|   [tools](https://github.com/simonmichael/hledger/issues?q=is:open+label:tools) (developer tools, infrastructure)             [bugs](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:tools) ([starter](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+label:%22good+first+issue%22+label:tools)*[easy](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+label:easy?+label:tools)*[other](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+BUG%22+-label:%22good+first+issue%22+-label:easy?+label:tools))                                                   [wishes](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+label:%22A+WISH%22+label:tools)               [PRs](https://github.com/simonmichael/hledger/issues?q=is:open+is:pr+label:tools)               [?](https://github.com/simonmichael/hledger/issues?q=is:open+is:issue+-label:%22A+BUG%22+-label:%22A+WISH%22+label:tools)
 | ||
|   --------------------------------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------ ----------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------
 | ||
| 
 | ||
| ### Milestones and Projects
 | ||
| 
 | ||
| Milestones are used a little bit to plan releases. In 2017 we
 | ||
| experimented with projects, but in 2018 milestones are in favour again..
 | ||
| 
 | ||
| ### Estimates
 | ||
| 
 | ||
| You might see some experiments in estimate tracking, where some issue
 | ||
| names might have a suffix noting estimated and spent time. Basic format:
 | ||
| \[ESTIMATEDTOTALTASKTIME\|TIMESPENTSOFAR\]. Examples: \`\`\` \[2\] two
 | ||
| hours estimated, no time spent \[..\] half an hour estimated (a dot is
 | ||
| \~a quarter hour, as in timedot format) \[1d\] one day estimated (a day
 | ||
| is \~4 hours) \[1w\] one week estimated (a week is \~5 days or \~20
 | ||
| hours) \[3\|2\] three hours estimated, about two hours spent so far
 | ||
| \[1\|1w\|2d\] first estimate one hour, second estimate one week, about
 | ||
| two days spent so far \`\`\` Estimates are always for the total time
 | ||
| cost (not time remaining). Estimates are not usually changed, a new
 | ||
| estimate is added instead. Numbers are very approximate, but better than
 | ||
| nothing.
 | ||
| 
 | ||
| ### Trello
 | ||
| 
 | ||
| The [trello board](http://trello.hledger.org) (trello.hledger.org) is an
 | ||
| old collection of wishlist items. This should probably be considered
 | ||
| deprecated.
 | ||
| 
 | ||
| ## Funding
 | ||
| 
 | ||
| My vision for the hledger project has always been for it to be "accountable" and "self-sustaining", possibly through new forms of incentivisation. 
 | ||
| Classic non-monetary FOSS communities are a beautiful and precious thing.
 | ||
| Adding money can change their dynamic.
 | ||
| Yet, we would enjoy having a lot more issues resolved, and a faster rate of progress.
 | ||
| So we experiment, gently.
 | ||
| 
 | ||
| Currently we use bounties as a way to encourage resolution of issues.
 | ||
| There are a few ways to do this:
 | ||
| 
 | ||
| 1. You or your organisation can offer a bounty simply by saying so on the issue.
 | ||
| 
 | ||
| 2. You can use Bountysource. A few hledger bounties have been completed there.
 | ||
| 
 | ||
| 3. You can use the new Open Collective process below.
 | ||
| 
 | ||
| Issues with bounties of any kind are marked with the `bounty` label.
 | ||
| The Bounty Manager is @simonmichael.
 | ||
| 
 | ||
| ### New bounty process
 | ||
| 
 | ||
| It currently looks like this, and will evolve:
 | ||
| 
 | ||
| - Issues are marked as bounties by @simonmichael. Feel free to suggest additional issues which should receive the bounty label.
 | ||
| 
 | ||
| - Bounties are paid from the hledger project's public Open Collective fund.
 | ||
|   By contributing to the fund as an individual or organisation, you enable more bounties.
 | ||
| 
 | ||
| - These OC bounties (unlike 1 and 2 above) have standard amounts.
 | ||
|   These may be adjusted over time, depending eg on the state of our funds.
 | ||
|   Our current bounty amounts are
 | ||
|   - level 1: 10 USD
 | ||
|   - level 2: 25 USD
 | ||
|   - level 3: 50 USD
 | ||
| 
 | ||
| - When you complete a bounty, submit an expense to Open Collective,
 | ||
|   for whichever of the above bounty amounts you think appropriate,
 | ||
|   based eg on time or expertise spent, how much you need it,
 | ||
|   how much remains in our fund for other bounties, etc.
 | ||
|   This will be reviewed by OC and (maybe ?) @simonmichael.
 | ||
|   Successful claims, like donations, will appear in our public OC ledger.
 | ||
| 
 | ||
| Our bounty amounts are small, and nothing like professional rates in most countries,
 | ||
| but they still establish a principle of sustainability,
 | ||
| and help us to experiment.
 | ||
| You are encouraged to claim your bounties,
 | ||
| though you can also choose to transfer them to a new issue of your choice.
 | ||
| 
 |