├── VERSION ├── hledger-web ├── config │ ├── robots.txt │ ├── favicon.ico │ ├── keter.yaml │ ├── settings.yml │ └── routes ├── messages │ └── en.msg ├── Setup.hs ├── templates │ ├── default-layout.hamlet │ ├── homepage.lucius │ ├── homepage.julius │ ├── homepage.hamlet │ └── default-layout-wrapper.hamlet ├── static │ ├── favicon.ico │ ├── select2.png │ ├── select2-spinner.gif │ ├── img │ │ ├── glyphicons-halflings.png │ │ └── glyphicons-halflings-white.png │ └── jquery.url.js ├── Handler │ ├── RootR.hs │ ├── JournalEditR.hs │ ├── JournalEntriesR.hs │ ├── RegisterR.hs │ ├── JournalR.hs │ └── Utils.hs ├── .ghci ├── tests │ ├── TestImport.hs │ ├── main.hs │ └── HomeTest.hs ├── Settings │ ├── Development.hs │ └── StaticFiles.hs ├── app │ └── main.hs ├── Hledger │ ├── Web.hs │ └── Web │ │ ├── Options.hs │ │ └── Main.hs ├── devel.hs ├── Import.hs ├── Application.hs ├── Settings.hs └── deploy │ └── Procfile ├── bin └── README.md ├── hledger ├── Setup.hs ├── hledger-cli.hs ├── tests │ └── suite.hs ├── hlint.hs └── Hledger │ └── Cli │ ├── Incomestatement.hs │ ├── Balancesheet.hs │ ├── Histogram.hs │ ├── Tests.hs │ ├── Cashflow.hs │ ├── Print.hs │ ├── Version.hs │ ├── Register.hs │ └── Stats.hs ├── hledger-lib ├── Setup.hs ├── tests │ └── suite.hs ├── Hledger.hs ├── Hledger │ ├── Data.hs │ ├── Data │ │ ├── Commodity.hs │ │ ├── Ledger.hs │ │ └── FormatStrings.hs │ ├── Read │ │ └── TimelogReader.hs │ └── Utils │ │ └── UTF8IOCompat.hs └── hledger-lib.cabal ├── extra ├── hledger-chart │ ├── Setup.hs │ ├── hledger-chart.hs │ ├── Hledger │ │ ├── Chart.hs │ │ └── Chart │ │ │ └── Options.hs │ └── hledger-chart.cabal ├── hledger-vty │ ├── Setup.hs │ ├── hledger-vty.hs │ ├── Hledger │ │ ├── Vty.hs │ │ └── Vty │ │ │ └── Options.hs │ └── hledger-vty.cabal ├── README ├── current.journal ├── all.journal ├── accountnames.hs ├── uniquify.hs ├── equity.hs ├── makefile └── aliases.sh ├── profs └── README.txt ├── tools ├── trhsx ├── criterionbench.hs ├── progressionbench.hs ├── regressiontest.py ├── dayssincetag.hs ├── unittest.hs ├── simplifyprof.hs ├── runhledgerhpc ├── generatejournal.hs ├── listbydeps.hs └── doctest.hs ├── site ├── images │ ├── mac.png │ ├── linux.png │ ├── sshot.png │ ├── windows.png │ ├── watchhours.png │ ├── hledger-charts-2.png │ ├── hledger-screen-1.png │ └── hledger-web-journal.png ├── js │ └── highslide │ │ ├── graphics │ │ ├── close.png │ │ ├── icon.gif │ │ ├── closeX.png │ │ ├── loader.gif │ │ ├── resize.gif │ │ ├── zoomin.cur │ │ ├── zoomout.cur │ │ ├── fullexpand.gif │ │ ├── geckodimmer.png │ │ ├── loader.white.gif │ │ └── outlines │ │ │ ├── beveled.png │ │ │ ├── drop-shadow.png │ │ │ ├── glossy-dark.png │ │ │ ├── outer-glow.png │ │ │ ├── rounded-black.png │ │ │ └── rounded-white.png │ │ └── highslide-ie6.css ├── api-frames.html ├── site.hs ├── templates │ └── default.html └── css │ └── style.css ├── data ├── sample.csv ├── sample.rules ├── sample.timelog ├── unicode.journal └── sample.journal ├── tests ├── unicode-error-message.test ├── null-accountname-component.test ├── unbalanced.test ├── print-acct-pattern.test ├── unicode-account-matching.test ├── balance-precision.test ├── unicode-print.test ├── eliding-print.test.notimplemented ├── print-desc-pattern.test ├── unicode-balance.test ├── parens-in-account-name.test ├── balance-depth.test ├── decimals-balance-failure.test ├── parse-posting-error-pos.test ├── balance-date2.test ├── parse-blank-description.test ├── print-long-account.test ├── register-date2.test ├── unicode-register.test ├── zero-handling.test ├── eliding-register.test.notimplemented ├── stats.test ├── unicode-description-matching.test ├── timelog-stack-overflow.test ├── no-such-file.test ├── print-date2.test ├── balance-custom-format.test ├── include.test ├── 94.test ├── timezone.test ├── parse-dates.test ├── balance-sample.test ├── commodities.test ├── filter-patterns.test ├── aliases.test ├── balance-eliding.test ├── virtual.test ├── status.test ├── 87-wrong-balance.test ├── register-depth.test ├── timelog.test ├── comments.test ├── parse-ledger-sample.test ├── default-commodity.test ├── register-intervals.test ├── read-csv.test ├── balance-assertions.test ├── tags.test ├── add.test ├── amount-layout-vertical.test └── precision.test ├── .authorspellings ├── .gitignore ├── DOCS.md ├── bench.tests ├── CSV.md ├── ALIASES.md ├── SCREENSHOTS.md ├── ANNOUNCE ├── README.md ├── HCAR.tex ├── CONTRIBUTORS.md └── .boring /VERSION: -------------------------------------------------------------------------------- 1 | 0.21.3 2 | -------------------------------------------------------------------------------- /hledger-web/config/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | -------------------------------------------------------------------------------- /hledger-web/messages/en.msg: -------------------------------------------------------------------------------- 1 | Hello: Hello 2 | -------------------------------------------------------------------------------- /bin/README.md: -------------------------------------------------------------------------------- 1 | Developer binaries built with the makefile go here. 2 | -------------------------------------------------------------------------------- /hledger/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /hledger-lib/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /hledger-web/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /extra/hledger-chart/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /extra/hledger-vty/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /profs/README.txt: -------------------------------------------------------------------------------- 1 | Developer profiling results, benchmark results etc. go here. 2 | -------------------------------------------------------------------------------- /tools/trhsx: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # fake trhsx, to help "make hledgermac" 3 | cp $2 $3 4 | -------------------------------------------------------------------------------- /extra/hledger-vty/hledger-vty.hs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env runhaskell 2 | import Hledger.Vty (main) 3 | -------------------------------------------------------------------------------- /extra/hledger-chart/hledger-chart.hs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env runhaskell 2 | import Hledger.Chart (main) 3 | -------------------------------------------------------------------------------- /site/images/mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/hledger/master/site/images/mac.png -------------------------------------------------------------------------------- /data/sample.csv: -------------------------------------------------------------------------------- 1 | "2012/3/22","TRANSFER TO SAVINGS","-10.00" 2 | "2012/3/23","SOMETHING ELSE","5.50" 3 | -------------------------------------------------------------------------------- /site/images/linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/hledger/master/site/images/linux.png -------------------------------------------------------------------------------- /site/images/sshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/hledger/master/site/images/sshot.png -------------------------------------------------------------------------------- /extra/README: -------------------------------------------------------------------------------- 1 | Extra stuff: less-maintained add-ons, custom report scripts, shell aliases, make rules... 2 | -------------------------------------------------------------------------------- /extra/current.journal: -------------------------------------------------------------------------------- 1 | ; current/recent years 2 | 3 | ;!include 2010.journal 4 | !include 2011.journal 5 | -------------------------------------------------------------------------------- /site/images/windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/hledger/master/site/images/windows.png -------------------------------------------------------------------------------- /hledger-web/templates/default-layout.hamlet: -------------------------------------------------------------------------------- 1 | $maybe msg <- mmsg 2 |
#{msg} 3 | ^{widget} 4 | -------------------------------------------------------------------------------- /site/images/watchhours.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/hledger/master/site/images/watchhours.png -------------------------------------------------------------------------------- /hledger-web/config/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/hledger/master/hledger-web/config/favicon.ico -------------------------------------------------------------------------------- /hledger-web/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/hledger/master/hledger-web/static/favicon.ico -------------------------------------------------------------------------------- /hledger-web/static/select2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/hledger/master/hledger-web/static/select2.png -------------------------------------------------------------------------------- /hledger-web/templates/homepage.lucius: -------------------------------------------------------------------------------- 1 | h1 { 2 | text-align: center 3 | } 4 | h2##{aDomId} { 5 | color: #990 6 | } 7 | -------------------------------------------------------------------------------- /site/images/hledger-charts-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/hledger/master/site/images/hledger-charts-2.png -------------------------------------------------------------------------------- /site/images/hledger-screen-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/hledger/master/site/images/hledger-screen-1.png -------------------------------------------------------------------------------- /site/images/hledger-web-journal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/hledger/master/site/images/hledger-web-journal.png -------------------------------------------------------------------------------- /site/js/highslide/graphics/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/hledger/master/site/js/highslide/graphics/close.png -------------------------------------------------------------------------------- /site/js/highslide/graphics/icon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/hledger/master/site/js/highslide/graphics/icon.gif -------------------------------------------------------------------------------- /hledger-web/static/select2-spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/hledger/master/hledger-web/static/select2-spinner.gif -------------------------------------------------------------------------------- /site/js/highslide/graphics/closeX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/hledger/master/site/js/highslide/graphics/closeX.png -------------------------------------------------------------------------------- /site/js/highslide/graphics/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/hledger/master/site/js/highslide/graphics/loader.gif -------------------------------------------------------------------------------- /site/js/highslide/graphics/resize.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/hledger/master/site/js/highslide/graphics/resize.gif -------------------------------------------------------------------------------- /site/js/highslide/graphics/zoomin.cur: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/hledger/master/site/js/highslide/graphics/zoomin.cur -------------------------------------------------------------------------------- /site/js/highslide/graphics/zoomout.cur: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/hledger/master/site/js/highslide/graphics/zoomout.cur -------------------------------------------------------------------------------- /site/js/highslide/graphics/fullexpand.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/hledger/master/site/js/highslide/graphics/fullexpand.gif -------------------------------------------------------------------------------- /site/js/highslide/graphics/geckodimmer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/hledger/master/site/js/highslide/graphics/geckodimmer.png -------------------------------------------------------------------------------- /site/js/highslide/graphics/loader.white.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/hledger/master/site/js/highslide/graphics/loader.white.gif -------------------------------------------------------------------------------- /hledger-web/static/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/hledger/master/hledger-web/static/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /site/js/highslide/graphics/outlines/beveled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/hledger/master/site/js/highslide/graphics/outlines/beveled.png -------------------------------------------------------------------------------- /tests/unicode-error-message.test: -------------------------------------------------------------------------------- 1 | hledgerdev -f - balance 2 | <<< 3 | 2009-01-01 broken entry 4 | дебит 1 5 | кредит -2 6 | >>>2 /дебит/ 7 | >>>= 1 8 | -------------------------------------------------------------------------------- /hledger-web/templates/homepage.julius: -------------------------------------------------------------------------------- 1 | document.getElementById("#{aDomId}").innerHTML = "This text was added by the Javascript part of the homepage widget."; 2 | -------------------------------------------------------------------------------- /site/js/highslide/graphics/outlines/drop-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/hledger/master/site/js/highslide/graphics/outlines/drop-shadow.png -------------------------------------------------------------------------------- /site/js/highslide/graphics/outlines/glossy-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/hledger/master/site/js/highslide/graphics/outlines/glossy-dark.png -------------------------------------------------------------------------------- /site/js/highslide/graphics/outlines/outer-glow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/hledger/master/site/js/highslide/graphics/outlines/outer-glow.png -------------------------------------------------------------------------------- /hledger-web/static/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/hledger/master/hledger-web/static/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /site/js/highslide/graphics/outlines/rounded-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/hledger/master/site/js/highslide/graphics/outlines/rounded-black.png -------------------------------------------------------------------------------- /site/js/highslide/graphics/outlines/rounded-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batterseapower/hledger/master/site/js/highslide/graphics/outlines/rounded-white.png -------------------------------------------------------------------------------- /data/sample.rules: -------------------------------------------------------------------------------- 1 | date-field 0 2 | description-field 1 3 | amount-field 2 4 | currency $ 5 | base-account assets:bank:checking 6 | 7 | SAVINGS 8 | assets:bank:savings 9 | -------------------------------------------------------------------------------- /tests/null-accountname-component.test: -------------------------------------------------------------------------------- 1 | # hledgerdev -f - balance -E 2 | # <<< 3 | # 2009/1/1 x 4 | # a: 13 5 | # b 6 | # >>>2 /accountname seems ill-formed: a:/ 7 | # >>>= 1 8 | -------------------------------------------------------------------------------- /tests/unbalanced.test: -------------------------------------------------------------------------------- 1 | hledgerdev -f - register 2 | <<< 3 | 2009/1/1 a 4 | b 1.1 5 | c -1 6 | >>>2 /could not balance this transaction \(real postings are off by 0.1\)/ 7 | >>>= 1 8 | -------------------------------------------------------------------------------- /hledger/hledger-cli.hs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env runhaskell 2 | -- the hledger command-line executable; see Hledger/Cli/Main.hs 3 | 4 | module Main (main) 5 | where 6 | import Hledger.Cli.Main (main) 7 | -------------------------------------------------------------------------------- /extra/all.journal: -------------------------------------------------------------------------------- 1 | ; all years 2 | 3 | !include 2006.journal 4 | !include 2007.journal 5 | !include 2008.journal 6 | !include 2009.journal 7 | !include 2010.journal 8 | !include 2011.journal 9 | -------------------------------------------------------------------------------- /data/sample.timelog: -------------------------------------------------------------------------------- 1 | i 2009/03/27 09:00:00 projects:a 2 | o 2009/03/27 17:00:34 3 | i 2009/03/31 22:21:45 personal:reading:online 4 | o 2009/04/01 02:00:34 5 | i 2009/04/02 09:00:00 projects:b 6 | o 2009/04/02 17:00:34 7 | -------------------------------------------------------------------------------- /tests/print-acct-pattern.test: -------------------------------------------------------------------------------- 1 | # print with negative account pattern should exclude transactions containing a matched posting 2 | hledgerdev -f- print not:a 3 | <<< 4 | 2010/1/1 x 5 | a 1 6 | b -1 7 | >>> 8 | >>>=0 9 | -------------------------------------------------------------------------------- /hledger-web/Handler/RootR.hs: -------------------------------------------------------------------------------- 1 | -- | Site root and misc. handlers. 2 | 3 | module Handler.RootR where 4 | 5 | import Import 6 | 7 | getRootR :: Handler RepHtml 8 | getRootR = redirect defaultroute where defaultroute = RegisterR 9 | -------------------------------------------------------------------------------- /hledger-web/.ghci: -------------------------------------------------------------------------------- 1 | :set -i.:config:dist/build/autogen 2 | :set -XCPP -XTemplateHaskell -XQuasiQuotes -XTypeFamilies -XFlexibleContexts -XGADTs -XOverloadedStrings -XMultiParamTypeClasses -XGeneralizedNewtypeDeriving -XEmptyDataDecls 3 | -------------------------------------------------------------------------------- /tests/unicode-account-matching.test: -------------------------------------------------------------------------------- 1 | hledgerdev -f - register τράπ 2 | <<< 3 | 2009-01-01 проверка 4 | τράπεζα 10 руб 5 | नकद 6 | >>> 7 | 2009/01/01 проверка τράπεζα 10 руб 10 руб 8 | >>>=0 9 | -------------------------------------------------------------------------------- /tests/balance-precision.test: -------------------------------------------------------------------------------- 1 | # 2 | hledgerdev -f- balance 3 | <<< 4 | 1/1 5 | a 1.00 6 | b -1 7 | >>> 8 | 1.00 a 9 | -1.00 b 10 | -------------------- 11 | 0 12 | >>>=0 13 | -------------------------------------------------------------------------------- /tests/unicode-print.test: -------------------------------------------------------------------------------- 1 | hledgerdev -f - print 2 | <<< 3 | 2009-01-01 проверка 4 | счёт:первый 1 5 | счёт:второй 6 | >>> 7 | 2009/01/01 проверка 8 | счёт:первый 1 9 | счёт:второй -1 10 | 11 | >>>=0 12 | -------------------------------------------------------------------------------- /hledger-lib/tests/suite.hs: -------------------------------------------------------------------------------- 1 | import Hledger (tests_Hledger) 2 | import Test.Framework.Providers.HUnit (hUnitTestToTests) 3 | import Test.Framework.Runners.Console (defaultMain) 4 | 5 | main :: IO () 6 | main = defaultMain $ hUnitTestToTests tests_Hledger 7 | -------------------------------------------------------------------------------- /tests/eliding-print.test.notimplemented: -------------------------------------------------------------------------------- 1 | hledgerdev -f- print 2 | <<< 3 | 2009/1/1 x 4 | aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa €1 5 | b 6 | >>> 7 | 2009/01/01 x 8 | aa:aaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa 9 | b 10 | -------------------------------------------------------------------------------- /tests/print-desc-pattern.test: -------------------------------------------------------------------------------- 1 | hledgerdev -f - print desc:x 2 | <<< 3 | 2009/1/1 x 4 | a 1 5 | b 6 | 7 | 2009/1/1 y 8 | a 1 9 | b 10 | 11 | >>> 12 | 2009/01/01 x 13 | a 1 14 | b -1 15 | 16 | >>>=0 17 | -------------------------------------------------------------------------------- /tests/unicode-balance.test: -------------------------------------------------------------------------------- 1 | hledgerdev -f - balance 2 | <<< 3 | 2009-01-01 проверка 4 | τράπεζα 10 руб 5 | नकद 6 | >>> 7 | 10 руб τράπεζα 8 | -10 руб नकद 9 | -------------------- 10 | 0 11 | >>>=0 12 | -------------------------------------------------------------------------------- /hledger/tests/suite.hs: -------------------------------------------------------------------------------- 1 | import Hledger.Cli (tests_Hledger_Cli) 2 | import Test.Framework.Providers.HUnit (hUnitTestToTests) 3 | import Test.Framework.Runners.Console (defaultMain) 4 | 5 | main :: IO () 6 | main = defaultMain $ hUnitTestToTests tests_Hledger_Cli 7 | -------------------------------------------------------------------------------- /tests/parens-in-account-name.test: -------------------------------------------------------------------------------- 1 | hledgerdev -f - print 2 | <<< 3 | 2009-01-01 x 4 | a 2 5 | b (b) b -1 6 | c 7 | >>> 8 | 2009/01/01 x 9 | a 2 10 | b (b) b -1 11 | c -1 12 | 13 | >>>=0 14 | -------------------------------------------------------------------------------- /data/unicode.journal: -------------------------------------------------------------------------------- 1 | ; unicode in description, account name and currency symbol 2 | 2010/1/1 ß 3 | (ß) 10 ß 4 | 5 | ; as above but with characters from code pages not installed on a western ms windows machine 6 | 2010/1/1 проверка 7 | (проверка) 10 проверка 8 | 9 | -------------------------------------------------------------------------------- /extra/accountnames.hs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env runhaskell 2 | -- list the default journal's chart of accounts in --flat style 3 | import Hledger 4 | main = do 5 | j <- myJournal 6 | let l = journalToLedger nullfilterspec{empty=True} j 7 | mapM_ putStrLn (accountnames l) 8 | -------------------------------------------------------------------------------- /tests/balance-depth.test: -------------------------------------------------------------------------------- 1 | # 1 2 | hledgerdev -f data/sample.journal balance --no-total --depth 1 3 | >>> 4 | $-1 assets 5 | $2 expenses 6 | $-2 income 7 | $1 liabilities 8 | >>>=0 9 | 10 | -------------------------------------------------------------------------------- /tests/decimals-balance-failure.test: -------------------------------------------------------------------------------- 1 | # b amount with no decimal places, in middle, causes balance failure (0.6.1) 2 | hledgerdev -f - print 3 | <<< 4 | 2009/1/1 x 5 | a $1.25 6 | b $-1 7 | c $-0.25 8 | 9 | >>>2 10 | >>>=0 11 | -------------------------------------------------------------------------------- /tests/parse-posting-error-pos.test: -------------------------------------------------------------------------------- 1 | # should give an accurate parse error location 2 | # hledgerdev -f- stat 3 | # <<< 4 | # 2010/1/1 x 5 | # a 1 6 | # b 7 | 8 | # 2010/1/1 y 9 | # c: 1 10 | # d 11 | 12 | # >>>2 /line 6, column 5/ 13 | # >>>= 1 14 | -------------------------------------------------------------------------------- /hledger-web/tests/TestImport.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module TestImport 3 | ( module Yesod.Test 4 | , module Foundation 5 | , Specs 6 | ) where 7 | 8 | import Yesod.Test 9 | 10 | import Foundation 11 | 12 | type Specs = YesodSpec App 13 | -------------------------------------------------------------------------------- /hledger-web/Settings/Development.hs: -------------------------------------------------------------------------------- 1 | module Settings.Development where 2 | 3 | import Prelude 4 | 5 | development :: Bool 6 | development = 7 | #if DEVELOPMENT 8 | True 9 | #else 10 | False 11 | #endif 12 | 13 | production :: Bool 14 | production = not development 15 | -------------------------------------------------------------------------------- /tests/balance-date2.test: -------------------------------------------------------------------------------- 1 | hledgerdev -f - balance -p 'in 2009' --date2 2 | <<< 3 | 2009/1/1 x 4 | a 1 5 | b 6 | 7 | 2009/1/1=2010/1/1 x 8 | a 10 9 | b 10 | >>> 11 | 1 a 12 | -1 b 13 | -------------------- 14 | 0 15 | >>>=0 16 | -------------------------------------------------------------------------------- /tests/parse-blank-description.test: -------------------------------------------------------------------------------- 1 | # 1. accept a blank description 2 | hledgerdev -f- print 3 | <<< 4 | 2010/1/1 5 | a 1 6 | b 7 | 8 | >>>=0 9 | 10 | # 2. same, but no separator space after the date 11 | hledgerdev -f- print 12 | <<< 13 | 2010/1/1 14 | a 1 15 | b 16 | 17 | >>>=0 18 | -------------------------------------------------------------------------------- /.authorspellings: -------------------------------------------------------------------------------- 1 | -- hints for darcs show author 2 | Tim Docker 3 | Nick Ingolia 4 | Marko Kocić 5 | Oliver Braun 6 | Gwern Branwen 7 | Michael Snoyman 8 | Trygve Laugstøl 9 | -------------------------------------------------------------------------------- /tests/print-long-account.test: -------------------------------------------------------------------------------- 1 | hledgerdev -f - print 2 | <<< 3 | 2009/1/1 x 4 | aaaaabbbbbcccccdddddeeeeefffffggggghhhhh 1 5 | b 6 | >>> 7 | 2009/01/01 x 8 | aaaaabbbbbcccccdddddeeeeefffffggggghhhhh 1 9 | b -1 10 | 11 | >>>=0 12 | -------------------------------------------------------------------------------- /tests/register-date2.test: -------------------------------------------------------------------------------- 1 | hledgerdev -f - register --date2 2 | <<< 3 | 2009/1/1=2010/1/1 x 4 | a 1 5 | b 6 | >>> 7 | 2010/01/01 x a 1 1 8 | b -1 0 9 | >>>=0 10 | -------------------------------------------------------------------------------- /tests/unicode-register.test: -------------------------------------------------------------------------------- 1 | hledgerdev -f - register 2 | <<< 3 | 2009-01-01 проверка 4 | τράπεζα 10 руб 5 | नकद 6 | >>> 7 | 2009/01/01 проверка τράπεζα 10 руб 10 руб 8 | नकद -10 руб 0 9 | >>>=0 10 | -------------------------------------------------------------------------------- /tests/zero-handling.test: -------------------------------------------------------------------------------- 1 | # a zero amount is always displayed as just "0", regardless of any commodity/decimal places/price (like ledger) 2 | # 3 | hledgerdev -f- print --empty 4 | <<< 5 | 2010/3/1 x 6 | a $0.00 @ 3EUR 7 | b 8 | >>> 9 | 2010/03/01 x 10 | a 0 11 | b 0 12 | 13 | >>>=0 14 | -------------------------------------------------------------------------------- /hledger-web/config/keter.yaml: -------------------------------------------------------------------------------- 1 | exec: ../dist/build/hledger-web/hledger-web 2 | args: 3 | - production 4 | host: <> 5 | 6 | # Use the following to automatically copy your bundle upon creation via `yesod 7 | # keter`. Uses `scp` internally, so you can set it to a remote destination 8 | # copy-to: user@host:/opt/keter/incoming 9 | -------------------------------------------------------------------------------- /tests/eliding-register.test.notimplemented: -------------------------------------------------------------------------------- 1 | hledgerdev -f- register 2 | <<< 3 | 2009/1/1 x aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa:aaaaaaaaaaaaaaaaaaaa €1 4 | b 5 | >>> 6 | 2009/01/01 x aa:aa:aaaaaaaaaaaaaaaa €1 €1 7 | b €-1 0 8 | -------------------------------------------------------------------------------- /tests/stats.test: -------------------------------------------------------------------------------- 1 | # 1. empty file 2 | hledgerdev -f- stats 3 | <<< 4 | >>> /Accounts.* 0 \(depth 0\)/ 5 | >>>=0 6 | 7 | # 2. included files should be listed in parse order 8 | touch a.j b.j; hledgerdev -f- stats; rm -f a.j b.j 9 | <<< 10 | include a.j 11 | include b.j 12 | >>> /Included journal files *: *\.\/a/ 13 | >>>2 14 | >>>=0 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.aes 2 | */dist/ 3 | *.hi 4 | *.o 5 | _* 6 | cabal-dev* 7 | hledger-web/static/tmp/ 8 | hledger-web/yesod-devel/ 9 | TAGS 10 | tags 11 | bin/* 12 | site/site 13 | site/*.md 14 | site/[0-9]* 15 | /config 16 | /messages 17 | /static 18 | /templates 19 | profs/profs 20 | t.* 21 | tt.* 22 | ttt.* 23 | tttt.* 24 | ttttt.* 25 | site/api 26 | .haddockprologue 27 | -------------------------------------------------------------------------------- /hledger-web/config/settings.yml: -------------------------------------------------------------------------------- 1 | Default: &defaults 2 | host: "*4" # any IPv4 host 3 | port: 3000 4 | approot: "http://localhost:3000" 5 | copyright: " " 6 | #analytics: UA-YOURCODE 7 | 8 | Development: 9 | <<: *defaults 10 | 11 | Testing: 12 | <<: *defaults 13 | 14 | Staging: 15 | <<: *defaults 16 | 17 | Production: 18 | #approot: "http://www.example.com" 19 | <<: *defaults 20 | -------------------------------------------------------------------------------- /tests/unicode-description-matching.test: -------------------------------------------------------------------------------- 1 | hledgerdev -f - register desc:аура 2 | <<< 3 | 2009-01-01 аура (cyrillic letters) 4 | bank 10 5 | cash 6 | 2010-01-01 aypa (roman letters) 7 | bank 20 8 | cash 9 | >>> 10 | 2009/01/01 аура (cyrillic le.. bank 10 10 11 | cash -10 0 12 | >>>=0 13 | -------------------------------------------------------------------------------- /hledger-web/app/main.hs: -------------------------------------------------------------------------------- 1 | import Prelude (IO) 2 | -- import Yesod.Default.Config (fromArgs) 3 | -- import Yesod.Default.Main (defaultMain) 4 | -- import Settings (parseExtra) 5 | -- import Application (makeApplication) 6 | 7 | import qualified Hledger.Web.Main 8 | 9 | main :: IO () 10 | -- main = defaultMain (fromArgs parseExtra) makeApplication 11 | main = Hledger.Web.Main.main 12 | -------------------------------------------------------------------------------- /tests/timelog-stack-overflow.test: -------------------------------------------------------------------------------- 1 | # this gave a stack space overflow error with 0.8-0.9 due to infinite 2 | # recursion in Posting and Transaction's equality tests: 3 | hledgerdev -f - balance 4 | <<< 5 | i 2010/1/1 09:00:00 a:b 6 | o 2010/1/1 09:03:00 7 | >>>=0 8 | # incidentally this didn't trigger it.. go figure 9 | #hledgerdev -f - balance 10 | #<<< 11 | #i 2010/1/1 09:00:00 a:b 12 | #o 2010/1/1 09:02:00 13 | #>>>=0 14 | -------------------------------------------------------------------------------- /tools/criterionbench.hs: -------------------------------------------------------------------------------- 1 | {- Criterion-based benchmarks. Criterion displays and minimises the impact 2 | of time variance and charts the results. -} 3 | 4 | import Criterion.Main 5 | import System.Environment (withArgs) 6 | import qualified HledgerMain 7 | 8 | main = defaultMain [ 9 | bench "balance_100x100x10" $ nfIO $ withArgs ["balance", "-f", "100x100x10.ledger", ">/dev/null"] HledgerMain.main 10 | ] 11 | -------------------------------------------------------------------------------- /tests/no-such-file.test: -------------------------------------------------------------------------------- 1 | # commands should generally autocreate an empty journal when missing, see also add*.test 2 | # $$ used for safe concurrent test running, may be a bash-ism 3 | 4 | hledgerdev register -f no-such-file-$$; rm -f no-such-file-$$ 5 | >>> 6 | >>>2 /journal file.*not found/ 7 | >>>=0 8 | 9 | hledgerdev balance --no-total -f no-such-file-$$; rm -f no-such-file-$$ 10 | >>> 11 | >>>2 /journal file.*not found/ 12 | >>>=0 13 | -------------------------------------------------------------------------------- /tools/progressionbench.hs: -------------------------------------------------------------------------------- 1 | {- Progression-based benchmarks. Progression charts the difference between successive benchmark runs. -} 2 | 3 | import Criterion.Main hiding (defaultMain, defaultMainWith) 4 | import Progression.Main 5 | import System.Environment (withArgs) 6 | import qualified HledgerMain 7 | 8 | main = defaultMain $ 9 | bench "balance_100x100x10" $ nfIO $ withArgs ["balance", "-f", "100x100x10.ledger", ">/dev/null"] HledgerMain.main 10 | -------------------------------------------------------------------------------- /hledger-web/config/routes: -------------------------------------------------------------------------------- 1 | /static StaticR Static getStatic 2 | /favicon.ico FaviconR GET 3 | /robots.txt RobotsR GET 4 | 5 | / RootR GET 6 | 7 | /journal JournalR GET POST 8 | /journal/entries JournalEntriesR GET POST 9 | /journal/edit JournalEditR GET POST 10 | 11 | /register RegisterR GET POST 12 | 13 | -- /accounts AccountsR GET 14 | -- /api/accounts AccountsJsonR GET 15 | -------------------------------------------------------------------------------- /hledger-web/Hledger/Web.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Re-export the modules of the hledger-web program. 3 | -} 4 | 5 | module Hledger.Web ( 6 | module Hledger.Web.Options, 7 | module Hledger.Web.Main, 8 | tests_Hledger_Web 9 | ) 10 | where 11 | import Test.HUnit 12 | 13 | import Hledger.Web.Options 14 | import Hledger.Web.Main 15 | 16 | tests_Hledger_Web :: Test 17 | tests_Hledger_Web = TestList 18 | [ 19 | -- tests_Hledger_Web_Options 20 | -- ,tests_Hledger_Web_Main 21 | ] 22 | -------------------------------------------------------------------------------- /extra/hledger-vty/Hledger/Vty.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Re-export the modules of the hledger-vty program. 3 | -} 4 | 5 | module Hledger.Vty ( 6 | module Hledger.Vty.Main, 7 | module Hledger.Vty.Options, 8 | tests_Hledger_Vty 9 | ) 10 | where 11 | import Test.HUnit 12 | 13 | import Hledger.Vty.Main 14 | import Hledger.Vty.Options 15 | 16 | tests_Hledger_Vty :: Test 17 | tests_Hledger_Vty = TestList 18 | [ 19 | -- tests_Hledger_Vty_Main 20 | -- tests_Hledger_Vty_Options 21 | ] 22 | -------------------------------------------------------------------------------- /hledger-web/Handler/JournalEditR.hs: -------------------------------------------------------------------------------- 1 | -- | /journal/edit handlers. 2 | 3 | module Handler.JournalEditR where 4 | 5 | import Import 6 | 7 | import Handler.Common 8 | import Handler.Post 9 | import Handler.Utils 10 | 11 | 12 | -- | The journal editform, no sidebar. 13 | getJournalEditR :: Handler RepHtml 14 | getJournalEditR = do 15 | vd <- getViewData 16 | defaultLayout $ do 17 | setTitle "hledger-web journal edit form" 18 | toWidget $ editform vd 19 | 20 | postJournalEditR :: Handler RepHtml 21 | postJournalEditR = handlePost 22 | -------------------------------------------------------------------------------- /tests/print-date2.test: -------------------------------------------------------------------------------- 1 | # print shows both dates. The second's year defaults to the first's. 2 | hledgerdev -f - print --date2 3 | <<< 4 | 2009/1/1=1/2 x 5 | a 1 6 | b 7 | >>> 8 | 2009/01/01=2009/01/02 x 9 | a 1 10 | b -1 11 | 12 | >>>2 13 | >>>= 0 14 | 15 | # Secondary date of 29 Feb on leap year should be valid 16 | hledgerdev -f - print --date2 17 | <<< 18 | 2001/2/27=2000/2/29 x 19 | a 1 20 | b 21 | >>> 22 | 2001/02/27=2000/02/29 x 23 | a 1 24 | b -1 25 | 26 | >>>2 27 | >>>= 0 28 | -------------------------------------------------------------------------------- /site/api-frames.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | hledger api docs 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | <p><a href="modules-index.html">non-frames version</a> 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/balance-custom-format.test: -------------------------------------------------------------------------------- 1 | hledgerdev -f data/sample.journal balance --format="%30(account) %-.20(total)" 2 | >>> 3 | assets $-1 4 | bank:saving $1 5 | cash $-2 6 | expenses $2 7 | food $1 8 | supplies $1 9 | income $-2 10 | gifts $-1 11 | salary $-1 12 | liabilities:debts $1 13 | -------------------- 14 | 0 15 | >>>= 0 16 | -------------------------------------------------------------------------------- /extra/hledger-chart/Hledger/Chart.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Re-export the modules of the hledger-chart program. 3 | -} 4 | 5 | module Hledger.Chart ( 6 | module Hledger.Chart.Main, 7 | module Hledger.Chart.Options, 8 | tests_Hledger_Chart 9 | ) 10 | where 11 | import Test.HUnit 12 | 13 | import Hledger.Chart.Main 14 | import Hledger.Chart.Options 15 | 16 | tests_Hledger_Chart :: Test 17 | tests_Hledger_Chart = TestList 18 | [ 19 | -- tests_Hledger_Chart_Main 20 | -- tests_Hledger_Chart_Options 21 | ] 22 | -------------------------------------------------------------------------------- /tests/include.test: -------------------------------------------------------------------------------- 1 | # nested includes in subdirectories 2 | mkdir -p b/c/d ; printf '2010/1/1\n (D) 1\n' >b/c/d/d.journal ; printf '2010/1/1\n (C) 1\n!include d/d.journal\n' >b/c/c.journal ; printf '2010/1/1\n (B) 1\n!include c/c.journal\n' >b/b.journal ; printf '2010/1/1\n (A) 1\n!include b/b.journal\n' >a.journal ; hledgerdev -f a.journal print; rm -rf a.journal b 3 | >>> 4 | 2010/01/01 5 | (A) 1 6 | 7 | 2010/01/01 8 | (B) 1 9 | 10 | 2010/01/01 11 | (C) 1 12 | 13 | 2010/01/01 14 | (D) 1 15 | 16 | >>>2 17 | >>>=0 18 | -------------------------------------------------------------------------------- /tests/94.test: -------------------------------------------------------------------------------- 1 | # issue 94: total balance should be that of top-level accounts, with and without --flat 2 | # 1. 3 | hledgerdev -f - balance 4 | <<< 5 | 1/1 6 | (a) 1 7 | 8 | 1/1 9 | (a:aa) 1 10 | >>> 11 | 2 a 12 | 1 aa 13 | -------------------- 14 | 2 15 | >>>= 0 16 | 17 | # 2. 18 | hledgerdev -f - balance --flat 19 | <<< 20 | 1/1 21 | (a) 1 22 | 23 | 1/1 24 | (a:aa) 1 25 | >>> 26 | 2 a 27 | 1 a:aa 28 | -------------------- 29 | 2 30 | >>>= 0 31 | 32 | -------------------------------------------------------------------------------- /tests/timezone.test: -------------------------------------------------------------------------------- 1 | # timezone-related tests 2 | # 1. as in ledger, historical prices may contain a time and timezone. 3 | # hledger ignores them and uses 00:00 local time instead. 4 | # XXX needs --value not --cost 5 | # hledgerdev -f - balance --no-total --cost 6 | # <<< 7 | # P 2011/01/01 00:00:00 A $1 8 | # P 2011/01/01 15:00:00-0100 A $2 9 | 10 | # 2010/12/31 11 | # (20101231) 1 A 12 | 13 | # 2011/1/1 14 | # (20110101) 1 A 15 | 16 | # 2011/1/2 17 | # (20110102) 1 A 18 | # >>> 19 | # 1 A 20101231 20 | # $2 20110101 21 | # $2 20110102 22 | # >>>=0 23 | -------------------------------------------------------------------------------- /hledger-lib/Hledger.hs: -------------------------------------------------------------------------------- 1 | module Hledger ( 2 | module Hledger.Data 3 | ,module Hledger.Query 4 | ,module Hledger.Read 5 | ,module Hledger.Reports 6 | ,module Hledger.Utils 7 | ,tests_Hledger 8 | ) 9 | where 10 | import Test.HUnit 11 | 12 | import Hledger.Data 13 | import Hledger.Query 14 | import Hledger.Read hiding (samplejournal) 15 | import Hledger.Reports 16 | import Hledger.Utils 17 | 18 | tests_Hledger = TestList 19 | [ 20 | tests_Hledger_Data 21 | ,tests_Hledger_Query 22 | ,tests_Hledger_Read 23 | ,tests_Hledger_Reports 24 | ] 25 | -------------------------------------------------------------------------------- /hledger-web/tests/main.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | {-# LANGUAGE NoMonomorphismRestriction #-} 3 | {-# OPTIONS_GHC -fno-warn-orphans #-} 4 | 5 | module Main where 6 | 7 | import Import 8 | import Yesod.Default.Config 9 | import Yesod.Test 10 | import Test.Hspec (hspec) 11 | import Application (makeFoundation) 12 | 13 | import HomeTest 14 | 15 | main :: IO () 16 | main = do 17 | conf <- Yesod.Default.Config.loadConfig $ (configSettings Testing) 18 | { csParseExtra = parseExtra 19 | } 20 | foundation <- makeFoundation conf 21 | hspec $ do 22 | yesodSpec foundation $ do 23 | homeSpecs 24 | -------------------------------------------------------------------------------- /tests/parse-dates.test: -------------------------------------------------------------------------------- 1 | # invalid dates should be rejected 2 | # 1. valid month and day, but flipped 3 | hledgerdev -f- print 4 | <<< 5 | 2010/31/12 x 6 | a 1 7 | b 8 | >>>2 /bad date/ 9 | >>>= 1 10 | # 2. too-large day 11 | hledgerdev -f- print 12 | <<< 13 | 2010/12/32 x 14 | a 1 15 | b 16 | >>>2 /bad date/ 17 | >>>= 1 18 | # 3. 29th feb on leap year should be ok 19 | hledgerdev -f- print 20 | <<< 21 | 2000/2/29 x 22 | a 1 23 | b 24 | >>> 25 | 2000/02/29 x 26 | a 1 27 | b -1 28 | 29 | >>>= 0 30 | # 4. 29th feb on non-leap year should fail 31 | hledgerdev -f- print 32 | <<< 33 | 2001/2/29 x 34 | a 1 35 | b 36 | >>>2 /bad date/ 37 | >>>= 1 38 | -------------------------------------------------------------------------------- /extra/uniquify.hs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env runhaskell 2 | {-| 3 | Uniquify journal entries based on some id in the description. Reads the 4 | default or specified journal, or stdin. 5 | 6 | Usage: uniquify.hs [-f JOURNALFILE | -f-] 7 | |-} 8 | 9 | import Data.List 10 | import Hledger 11 | import Hledger.Cli 12 | 13 | main = do 14 | opts <- getHledgerOpts 15 | withJournalDo opts uniquifyAndPrint 16 | 17 | uniquifyAndPrint :: CliOpts -> Journal -> IO () 18 | uniquifyAndPrint opts j@Journal{jtxns=ts} = print' opts j{jtxns=uniquify ts} 19 | where 20 | uniquify = nubBy (\t1 t2 -> extractId (tdescription t1) == extractId (tdescription t2)) 21 | extractId desc = desc -- extract some part that's supposed to be unique 22 | -------------------------------------------------------------------------------- /data/sample.journal: -------------------------------------------------------------------------------- 1 | ; A sample journal file. 2 | ; 3 | ; Sets up this account tree: 4 | ; assets 5 | ; bank 6 | ; checking 7 | ; saving 8 | ; cash 9 | ; expenses 10 | ; food 11 | ; supplies 12 | ; income 13 | ; gifts 14 | ; salary 15 | ; liabilities 16 | ; debts 17 | 18 | 2008/01/01 income 19 | assets:bank:checking $1 20 | income:salary 21 | 22 | 2008/06/01 gift 23 | assets:bank:checking $1 24 | income:gifts 25 | 26 | 2008/06/02 save 27 | assets:bank:saving $1 28 | assets:bank:checking 29 | 30 | 2008/06/03 * eat & shop 31 | expenses:food $1 32 | expenses:supplies $1 33 | assets:cash 34 | 35 | 2008/12/31 * pay off 36 | liabilities:debts $1 37 | assets:bank:checking 38 | 39 | 40 | ;final comment 41 | -------------------------------------------------------------------------------- /hledger-web/tests/HomeTest.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module HomeTest 3 | ( homeSpecs 4 | ) where 5 | 6 | import TestImport 7 | 8 | homeSpecs :: Specs 9 | homeSpecs = 10 | ydescribe "Some hledger-web tests" $ 11 | 12 | yit "serves a reasonable-looking register page" $ do 13 | get RegisterR 14 | statusIs 200 15 | bodyContains "accounts" 16 | 17 | -- post "/" $ do 18 | -- addNonce 19 | -- fileByLabel "Choose a file" "tests/main.hs" "text/plain" -- talk about self-reference 20 | -- byLabel "What's on the file?" "Some Content" 21 | 22 | -- statusIs 200 23 | -- htmlCount ".message" 1 24 | -- htmlAllContain ".message" "Some Content" 25 | -- htmlAllContain ".message" "text/plain" 26 | -------------------------------------------------------------------------------- /hledger/hlint.hs: -------------------------------------------------------------------------------- 1 | {- 2 | hlint configuration for hledger 3 | 4 | manual: http://community.haskell.org/~ndm/darcs/hlint/hlint.htm 5 | 6 | examples: 7 | ignore "Eta reduce" = "" - suppress all eta reduction suggestions. 8 | ignore "Eta reduce" = Data.List Prelude - suppress eta reduction hints in the Prelude and Data.List modules. 9 | ignore = Data.List.map - don't give any hints in the function Data.List.map. 10 | error = Data.List.map - any hint in the function is an error. 11 | error "Use concatMap" = "" - the hint to use concatMap is an error. 12 | warn "Use concatMap" = "" - the hint to use concatMap is a warning. 13 | 14 | -} 15 | 16 | import HLint.Default 17 | ignore "Use camelCase" = "" 18 | -------------------------------------------------------------------------------- /tests/balance-sample.test: -------------------------------------------------------------------------------- 1 | # 1. 2 | hledgerdev -f data/sample.journal balance 3 | >>> 4 | $-1 assets 5 | $1 bank:saving 6 | $-2 cash 7 | $2 expenses 8 | $1 food 9 | $1 supplies 10 | $-2 income 11 | $-1 gifts 12 | $-1 salary 13 | $1 liabilities:debts 14 | -------------------- 15 | 0 16 | >>>=0 17 | 18 | # 2. 19 | hledgerdev -f data/sample.journal balance o 20 | >>> 21 | $1 expenses:food 22 | $-2 income 23 | $-1 gifts 24 | $-1 salary 25 | -------------------- 26 | $-1 27 | >>>=0 28 | 29 | -------------------------------------------------------------------------------- /tests/commodities.test: -------------------------------------------------------------------------------- 1 | # a commodity may contain/end with numbers, if double quoted 2 | # 1. without quotes, fail. XXX parse error should be clearer here 3 | hledgerdev -f- print 4 | <<< 5 | 2010-04-05 x 6 | a 10 DE0002635307 7 | b 8 | >>>2 /unexpected "0"/ 9 | >>>= 1 10 | # 2. with quotes, ok; quotes appear in print output 11 | hledgerdev -f- print 12 | <<< 13 | 2010-04-05 x 14 | a 10 "DE 0002 635307" 15 | b 16 | >>> 17 | 2010/04/05 x 18 | a 10 "DE 0002 635307" 19 | b -10 "DE 0002 635307" 20 | 21 | >>>=0 22 | 23 | # 3. and in other reports too, I guess 24 | hledgerdev -f- balance 25 | <<< 26 | 2010-04-05 x 27 | a 10 "DE0002635307" 28 | b 29 | >>> 30 | 10 "DE0002635307" a 31 | -10 "DE0002635307" b 32 | -------------------- 33 | 0 34 | >>>=0 35 | -------------------------------------------------------------------------------- /hledger-web/devel.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE PackageImports #-} 2 | import "hledger-web" Application (getApplicationDev) 3 | import Network.Wai.Handler.Warp (runSettings, defaultSettings, settingsPort) 4 | import Control.Concurrent (forkIO) 5 | import System.Directory (doesFileExist, removeFile) 6 | import System.Exit (exitSuccess) 7 | import Control.Concurrent (threadDelay) 8 | 9 | main :: IO () 10 | main = do 11 | putStrLn "Starting devel application" 12 | (port, app) <- getApplicationDev 13 | forkIO $ 14 | runSettings defaultSettings 15 | { settingsPort = port 16 | } app 17 | loop 18 | 19 | loop :: IO () 20 | loop = do 21 | threadDelay 100000 22 | e <- doesFileExist "yesod-devel/devel-terminate" 23 | if e then terminateDevel else loop 24 | 25 | terminateDevel :: IO () 26 | terminateDevel = exitSuccess 27 | -------------------------------------------------------------------------------- /extra/equity.hs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env runhaskell 2 | {- 3 | Print an entry posting the total balance of the specified account and 4 | subaccounts, or all accounts, from the default journal. Like ledger's 5 | equity command. Useful when starting a new journal or closing the books. 6 | 7 | Usage: equity.hs [ACCTPAT] 8 | -} 9 | import Hledger 10 | import Hledger.Cli 11 | import System.Environment 12 | 13 | main = do 14 | j <- myJournal 15 | d <- getCurrentDay 16 | args <- getArgs 17 | let acctpat = head $ args ++ [""] 18 | (acctbals,_) = balanceReport [Flat] (optsToFilterSpec [] [acctpat] d) j 19 | txn = nulltransaction{ 20 | tdate=d, 21 | tpostings=[nullposting{paccount=a,pamount=b} | (a,_,_,b) <- acctbals] 22 | ++ [nullposting{paccount="equity:opening balances",pamount=missingamt}]} 23 | putStr $ show txn 24 | -------------------------------------------------------------------------------- /tools/regressiontest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # test whether hledger output matches ledger's 3 | # Simon Michael 2007 4 | 5 | from os import * 6 | from posix import * 7 | 8 | files = [ 9 | 'data/test.dat', 10 | 'data/test1.dat', 11 | # getenv('LEDGER'), 12 | ] 13 | 14 | commands = [ 15 | 'balance petty', 16 | '-s balance', 17 | '-s balance cash', 18 | 'register', 19 | 'register cash', 20 | 'print', 21 | ] 22 | 23 | do = system 24 | rule = lambda s: "="*30 + s + "="*30 25 | 26 | def regtest(file, cmd): 27 | """Print a heading and the diff of ledger and hledger output. No diff 28 | output is good.""" 29 | print rule('%s:%s' % (file,cmd)) 30 | putenv('LEDGER',file) 31 | do('ledger %s >1; ./hledger.hs %s >2; diff 1 2' % (cmd,cmd)) 32 | 33 | for f in files: 34 | for c in commands: 35 | regtest(f,c) 36 | 37 | -------------------------------------------------------------------------------- /tools/dayssincetag.hs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env runhaskell 2 | {- 3 | Display the current darcs repository's last tag and the number of days since. 4 | Similar to: 5 | $ darcs changes --to-tag . --from-tag .|head -n 1 |cut -d' ' -f-7 |xargs -I {} date -d "{}" +%s |xargs -I {} expr \( $(date +%s) - {} \) / 60 / 60 / 24 6 | -} 7 | import Data.Time 8 | import System.Environment 9 | import System.Locale 10 | import System.Process 11 | 12 | main = do 13 | tag:_ <- getArgs 14 | s <- readProcess "darcs" ["changes","--from-tag",tag,"--to-tag",tag] "" 15 | let datestr = unwords $ take 6 $ words $ head $ lines s 16 | date = readTime defaultTimeLocale "%a %b %e %H:%M:%S %Z %Y" datestr :: Day 17 | today <- getCurrentDay 18 | putStrLn $ show (diffDays today date) ++ " days since tag "++tag++":\n" 19 | putStr s 20 | 21 | getCurrentDay = do 22 | t <- getZonedTime 23 | return $ localDay (zonedTimeToLocalTime t) 24 | -------------------------------------------------------------------------------- /tests/filter-patterns.test: -------------------------------------------------------------------------------- 1 | # 1. account pattern with space 2 | hledgerdev -f- register 'a a' 3 | <<< 4 | 2010/3/1 x 5 | a a 1 6 | b 7 | >>> 8 | 2010/03/01 x a a 1 1 9 | >>>=0 10 | 11 | # 12 | # 2. description pattern with space 13 | hledgerdev -f- register desc:'x x' 14 | <<< 15 | 2010/3/1 x 16 | a 1 17 | b 18 | 19 | 2010/3/2 x x 20 | a 1 21 | b 22 | >>> 23 | 2010/03/02 x x a 1 1 24 | b -1 0 25 | >>>=0 26 | 27 | # 28 | # 3. multiple patterns, spaced and punctuated patterns 29 | hledgerdev -f- register 'a a' "'b" 30 | <<< 31 | 2011/9/11 32 | a a 1 33 | 'b 34 | >>> 35 | 2011/09/11 a a 1 1 36 | 'b -1 0 37 | >>>=0 38 | -------------------------------------------------------------------------------- /DOCS.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: hledger docs 3 | --- 4 | 5 | # Docs 6 | 7 | **Official hledger docs:** 8 | 9 | [Installation Guide](INSTALL.html)\ 10 | [Release Notes](NEWS.html)\ 11 | **[User Manual](MANUAL.html)** (and past versions: 12 | [0.20](0.20/MANUAL.html), 13 | [0.19](0.19/MANUAL.html), 14 | [0.18](0.18/MANUAL.html))\ 15 | [Developer Guide](DEVELOP.html)\ 16 | [FAQ](FAQ.html)\ 17 | 18 | 19 | **Tutorials:** 20 | 21 | [How to read CSV files](CSV.html)\ 22 | [How to use account aliases](ALIASES.html)\ 23 | 24 | 25 | **More docs:** 26 | 27 | [hledger.org/wiki](http://hledger.org/wiki) 28 | 29 | **Related:** 30 | 31 | [Accounting For Dragons](http://podcastle.org/2009/10/09/pc-miniature-38-accounting-for-dragons) *informative!*\ 32 | [ledger](http://ledger-cli.org) and its [manual](http://ledger-cli.org/3.0/doc/ledger3.html) *explanatory!*\ 33 | [beancount](http://furius.ca/beancount/), 34 | [penny](https://github.com/massysett/penny), 35 | [UMM](http://hackage.haskell.org/package/UMM) 36 | *more ledger-likes!*\ 37 | 38 | -------------------------------------------------------------------------------- /bench.tests: -------------------------------------------------------------------------------- 1 | # tests for "make benchmark" 2 | # one command per line, without the executable 3 | 4 | -f data/100x100x10.journal balance 5 | -f data/1000x1000x10.journal balance 6 | #-f data/1000x10000x10.journal balance 7 | -f data/10000x1000x10.journal balance 8 | -f data/10000x1000x10.journal balance aa 9 | #-f data/10000x10000x10.journal balance 10 | #-f data/100000x1000x10.journal balance 11 | 12 | -f data/100x100x10.journal register 13 | -f data/1000x1000x10.journal register 14 | #-f data/1000x10000x10.journal register 15 | #-f data/10000x1000x10.journal register 16 | #-f data/10000x10000x10.journal register 17 | #-f data/10000x1000x10.journal register aa 18 | #-f data/100000x1000x10.journal register 19 | 20 | -f data/100x100x10.journal print 21 | -f data/1000x1000x10.journal print 22 | #-f data/1000x10000x10.journal print 23 | -f data/10000x1000x10.journal print 24 | #-f data/10000x10000x10.journal print 25 | #-f data/10000x1000x10.journal print aa 26 | #-f data/100000x1000x10.journal print 27 | -------------------------------------------------------------------------------- /tools/unittest.hs: -------------------------------------------------------------------------------- 1 | {- 2 | A standalone unit test runner using test-framework. Compared to hledger's 3 | built-in test runner, this one shows verbose ansi-colored hierarchic 4 | results, can run tests in parallel, and may have better quickcheck support. 5 | -} 6 | 7 | import Test.Framework (defaultMain {-, testGroup-}) 8 | import Test.Framework.Providers.HUnit (hUnitTestToTests) 9 | --import Test.Framework.Providers.QuickCheck2 (testProperty) 10 | import Test.HUnit hiding (Test) 11 | import qualified Test.HUnit (Test) 12 | --import Test.QuickCheck 13 | import Tests (tests) 14 | import System.Exit (-- exitFailure, exitWith, 15 | ExitCode(..)) 16 | import System.IO (hGetContents, hPutStr, hFlush, stderr, stdout) 17 | import Text.Printf (printf) 18 | --import Text.ParserCombinators.Parsec 19 | import Control.Monad (liftM,when) 20 | import Data.Maybe (fromMaybe) 21 | import System.Process (runInteractiveCommand, waitForProcess) 22 | 23 | main :: IO () 24 | main = defaultMain $ concatMap hUnitTestToTests tests 25 | -------------------------------------------------------------------------------- /tests/aliases.test: -------------------------------------------------------------------------------- 1 | # alias-related tests 2 | 3 | # 1. command-line --alias option. Note multiple applicable aliases, but 4 | # only one is applied per account name. Spaces are allowed if quoted. 5 | hledgerdev -f- print --alias 'a a=A' --alias b=B 6 | <<< 7 | 2011/01/01 8 | a a 1 9 | c 10 | 11 | >>> 12 | 2011/01/01 13 | A 1 14 | c -1 15 | 16 | >>>=0 17 | 18 | # 2. alias directive, and an account with unbalanced posting indicators. 19 | hledgerdev -f- print 20 | <<< 21 | alias b=B 22 | 23 | 2011/01/01 24 | (b) 1 25 | 26 | >>> 27 | 2011/01/01 28 | (B) 1 29 | 30 | >>>=0 31 | 32 | # 3. --alias options run after alias directives. Subaccounts are also 33 | # matched and rewritten. Accounts with an internal part matching the alias 34 | # are ignored. 35 | hledgerdev -f- print --alias a=A --alias B=C 36 | <<< 37 | alias a=B 38 | 39 | 2011/01/01 40 | [a:x] 1 41 | [x:a:x] 42 | 43 | >>> 44 | 2011/01/01 45 | [C:x] 1 46 | [x:a:x] -1 47 | 48 | >>>2 49 | >>>=0 50 | -------------------------------------------------------------------------------- /tests/balance-eliding.test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env shelltest 2 | # 1. One commodity. Zero accounts should be elided but the final total should not. 3 | hledgerdev -f - balance 4 | <<< 5 | 2010/04/01 tr1 6 | a 16$ 7 | b -16$ 8 | 9 | 2010/04/02 tr2 10 | a -16$ 11 | b 16$ 12 | >>> 13 | -------------------- 14 | 0 15 | >>>=0 16 | 17 | # 2. An uninteresting parent account (with same balance as its single subaccount) is elided by default, like ledger 18 | hledgerdev -f - balance --no-total 19 | <<< 20 | 1/1 21 | (a:b) 1 22 | >>> 23 | 1 a:b 24 | >>>=0 25 | 26 | # 3. But not with --no-elide 27 | hledgerdev -f - balance --no-total --no-elide 28 | <<< 29 | 1/1 30 | (a:b) 1 31 | >>> 32 | 1 a 33 | 1 b 34 | >>>=0 35 | 36 | # 4. Nor when it has more than one subaccount 37 | hledgerdev -f - balance --no-total 38 | <<< 39 | 1/1 40 | (a:b) 1 41 | (a:c) -1 42 | >>> 43 | 0 a 44 | 1 b 45 | -1 c 46 | >>>2 47 | >>>=0 48 | 49 | -------------------------------------------------------------------------------- /hledger-web/Import.hs: -------------------------------------------------------------------------------- 1 | module Import 2 | ( module Import 3 | ) where 4 | 5 | import Prelude as Import hiding (head, init, last, 6 | readFile, tail, writeFile) 7 | import Yesod as Import hiding (Route (..)) 8 | 9 | import Control.Applicative as Import (pure, (<$>), (<*>)) 10 | import Data.Text as Import (Text) 11 | 12 | import Foundation as Import 13 | import Settings as Import 14 | import Settings.Development as Import 15 | import Settings.StaticFiles as Import 16 | 17 | #if __GLASGOW_HASKELL__ >= 704 18 | import Data.Monoid as Import 19 | (Monoid (mappend, mempty, mconcat), 20 | (<>)) 21 | #else 22 | import Data.Monoid as Import 23 | (Monoid (mappend, mempty, mconcat)) 24 | 25 | infixr 5 <> 26 | (<>) :: Monoid m => m -> m -> m 27 | (<>) = mappend 28 | #endif 29 | -------------------------------------------------------------------------------- /tools/simplifyprof.hs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env runhaskell 2 | -- simplifyprof.hs somefile.prof 3 | -- filter uninteresting fields from GHC profile output 4 | -- tested with GHC 6.8 5 | -- Simon Michael 2007,2008 6 | 7 | import Data.List 8 | import System.Environment 9 | import Text.Printf 10 | 11 | main = do 12 | args <- getArgs 13 | let f = head args 14 | s <- readFile f 15 | let ls = lines s 16 | let (firstpart, secondpart) = break ("individual inherited" `isInfixOf`) ls 17 | putStr $ unlines firstpart 18 | let fields = map getfields $ filter (not . null) $ drop 2 secondpart 19 | let maxnamelen = maximum $ map (length . head) fields 20 | let fmt = "%-" ++ show maxnamelen ++ "s %10s %5s %6s %9s %10s" 21 | putStrLn $ showheading fmt 22 | putStr $ unlines $ map (format fmt) fields 23 | 24 | getfields s = name:rest 25 | where 26 | space = takeWhile (==' ') s 27 | fields = words s 28 | name = space ++ head fields 29 | rest = drop 3 fields 30 | 31 | showheading fmt = format fmt ["cost centre","entries","%time","%alloc","%time-inh","%alloc-inh"] 32 | 33 | format fmt (s1:s2:s3:s4:s5:s6:[]) = printf fmt s1 s2 s3 s4 s5 s6 34 | -------------------------------------------------------------------------------- /tests/virtual.test: -------------------------------------------------------------------------------- 1 | # 1. virtual posting shouldn't affect balance 2 | hledgerdev -f- print 3 | <<< 4 | 2009/1/1 x 5 | (virtual) 100 6 | a 1 7 | b 8 | >>>=0 9 | # 10 | # 2. balanced virtual postings should be required to balance (themselves) 11 | hledgerdev -f- print 12 | <<< 13 | 2010/1/1 x 14 | [balanced virtual] 10 15 | a 1 16 | b 17 | >>>= !0 18 | # 19 | # 3. balanced virtual postings should be required to balance (themselves) 20 | hledgerdev -f- print 21 | <<< 22 | 2010/1/1 x 23 | [balanced virtual] 10 24 | [balanced virtual] -10 25 | a 1 26 | b 27 | >>>=0 28 | # 29 | # 4. a virtual posting with implicit amount should be handled correctly 30 | hledgerdev -f- print 31 | <<< 32 | 2010/1/1 x 33 | [a] 10 34 | [b] 35 | >>>=0 36 | # 37 | # 5. real and balanced virtual postings are balanced separately, and multiple blank virtuals are ok 38 | hledgerdev -f- balance 39 | <<< 40 | 2010/1/1 x 41 | a 1 42 | b 43 | [e] 10 44 | [f] 45 | (c) 46 | (d) 47 | >>> 48 | 1 a 49 | -1 b 50 | 10 e 51 | -10 f 52 | -------------------- 53 | 0 54 | >>>=0 55 | -------------------------------------------------------------------------------- /tools/runhledgerhpc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # runhledgerhpc "HPCCOMMAND [HPCARGS]" [HLEDGERARGS] 3 | # 4 | # A front-end that resets the tix count, runs hledgerhpc with the 5 | # specified hledger args, and runs hpc with the specified hpc args. 6 | # Should be run from hledger's top source directory. 7 | # 8 | # Eg: 9 | # hledger$ tools/runhledgerhpc report test 10 | # hledger$ tools/runhledgerhpc "markup --destdir=coverage" test 'some unit test' 11 | 12 | hledgerhpc="hledgerhpc" 13 | verbosity = 0 # 0=no output, 1=stderr only, 2=stdout+stderr 14 | 15 | import sys, os 16 | 17 | hpcargs, hledgerargs = sys.argv[1], ' '.join(sys.argv[2:]) 18 | 19 | # remove old tix files 20 | os.system("rm -f %s.tix" % hledgerhpc) 21 | 22 | # run the hpc-enabled binary with the specified hledger arguments to generate tix files 23 | if verbosity<1: 24 | os.system("bin/%s %s >/dev/null 2>&1" % (hledgerhpc,hledgerargs)) 25 | elif verbosity==1: 26 | os.system("bin/%s %s >/dev/null" % (hledgerhpc,hledgerargs)) 27 | else: 28 | os.system("bin/%s %s" % (hledgerhpc,hledgerargs)) 29 | 30 | # run the specified hpc command on the tix files 31 | os.system("hpc %s %s" % (hpcargs,hledgerhpc)) 32 | -------------------------------------------------------------------------------- /hledger-web/Settings/StaticFiles.hs: -------------------------------------------------------------------------------- 1 | module Settings.StaticFiles where 2 | 3 | import Prelude (IO, putStrLn, (++), (>>), return) 4 | import System.IO (stdout, hFlush) 5 | import Yesod.Static 6 | import qualified Yesod.Static as Static 7 | import Settings (staticDir) 8 | import Settings.Development 9 | 10 | -- | use this to create your static file serving site 11 | -- staticSite :: IO Static.Static 12 | -- staticSite = if development then Static.staticDevel staticDir 13 | -- else Static.static staticDir 14 | -- 15 | -- | This generates easy references to files in the static directory at compile time, 16 | -- giving you compile-time verification that referenced files exist. 17 | -- Warning: any files added to your static directory during run-time can't be 18 | -- accessed this way. You'll have to use their FilePath or URL to access them. 19 | -- $(staticFiles Settings.staticDir) 20 | 21 | 22 | staticSite :: IO Static.Static 23 | staticSite = 24 | if development 25 | then (do 26 | putStrLn ("Using web files from: " ++ staticDir ++ "/") >> hFlush stdout 27 | Static.staticDevel staticDir) 28 | else (do 29 | -- putStrLn "Using built-in web files" >> hFlush stdout 30 | return $(Static.embed staticDir)) 31 | 32 | $(publicFiles staticDir) 33 | -------------------------------------------------------------------------------- /hledger-web/Handler/JournalEntriesR.hs: -------------------------------------------------------------------------------- 1 | -- | /journal/entries handlers. 2 | 3 | module Handler.JournalEntriesR where 4 | 5 | import Import 6 | 7 | import Handler.Common 8 | import Handler.Post 9 | import Handler.Utils 10 | 11 | import Hledger.Data 12 | import Hledger.Query 13 | import Hledger.Reports 14 | import Hledger.Cli.Options 15 | import Hledger.Web.Options 16 | 17 | 18 | -- | The journal entries view, with sidebar. 19 | getJournalEntriesR :: Handler RepHtml 20 | getJournalEntriesR = do 21 | vd@VD{..} <- getViewData 22 | staticRootUrl <- (staticRoot . settings) <$> getYesod 23 | let 24 | sidecontent = sidebar vd 25 | title = "Journal entries" ++ if m /= Any then ", filtered" else "" :: String 26 | maincontent = entriesReportAsHtml opts vd $ entriesReport (reportopts_ $ cliopts_ opts) Any $ filterJournalTransactions m j 27 | defaultLayout $ do 28 | setTitle "hledger-web journal" 29 | toWidget [hamlet| 30 | ^{topbar vd} 31 | 32 | 33 | ^{sidecontent} 34 | 35 | 36 | #{title} 37 | ^{searchform vd} 38 | ^{maincontent} 39 | ^{addform staticRootUrl vd} 40 | ^{editform vd} 41 | ^{importform} 42 | |] 43 | 44 | postJournalEntriesR :: Handler RepHtml 45 | postJournalEntriesR = handlePost 46 | 47 | -------------------------------------------------------------------------------- /hledger/Hledger/Cli/Incomestatement.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE QuasiQuotes, TemplateHaskell, OverloadedStrings, NoCPP #-} 2 | {-| 3 | 4 | The @incomestatement@ command prints a simple income statement (profit & loss) report. 5 | 6 | -} 7 | 8 | module Hledger.Cli.Incomestatement ( 9 | incomestatement 10 | ,tests_Hledger_Cli_Incomestatement 11 | ) where 12 | 13 | import qualified Data.Text.Lazy.IO as LT 14 | import Test.HUnit 15 | import Text.Shakespeare.Text 16 | 17 | import Hledger 18 | import Hledger.Cli.Options 19 | import Hledger.Cli.Balance 20 | 21 | -- | Print a simple income statement. 22 | incomestatement :: CliOpts -> Journal -> IO () 23 | incomestatement CliOpts{reportopts_=ropts} j = do 24 | d <- getCurrentDay 25 | let q = queryFromOpts d ropts 26 | incomereport@(_,income) = accountsReport ropts (And [q, journalIncomeAccountQuery j]) j 27 | expensereport@(_,expenses) = accountsReport ropts (And [q, journalExpenseAccountQuery j]) j 28 | total = income + expenses 29 | LT.putStr $ [lt|Income Statement 30 | 31 | Revenues: 32 | #{unlines $ accountsReportAsText ropts incomereport} 33 | Expenses: 34 | #{unlines $ accountsReportAsText ropts expensereport} 35 | 36 | Total: 37 | -------------------- 38 | #{padleft 20 $ showMixedAmountWithoutPrice total} 39 | |] 40 | 41 | tests_Hledger_Cli_Incomestatement :: Test 42 | tests_Hledger_Cli_Incomestatement = TestList 43 | [ 44 | ] 45 | -------------------------------------------------------------------------------- /tests/status.test: -------------------------------------------------------------------------------- 1 | # filtering by transaction status 2 | 3 | # 1. with --cleared, print shows cleared transactions only 4 | hledgerdev -f- print --cleared 5 | <<< 6 | 2010/1/1 x 7 | a 1 8 | b 9 | 10 | 2010/1/2 * x 11 | a 1 12 | b 13 | 14 | 2010/1/3 * 15 | a 1 16 | b 17 | >>> 18 | 2010/01/02 * x 19 | a 1 20 | b -1 21 | 22 | 2010/01/03 * 23 | a 1 24 | b -1 25 | 26 | >>>=0 27 | 28 | # 2. with --uncleared, shows uncleared transactions only 29 | hledgerdev -f- print --uncleared 30 | <<< 31 | 2010/1/1 x 32 | a 1 33 | b 34 | 35 | 2010/1/2 * x 36 | a 1 37 | b 38 | 39 | 2010/1/3 * 40 | a 1 41 | b 42 | >>> 43 | 2010/01/01 x 44 | a 1 45 | b -1 46 | 47 | >>>=0 48 | 49 | # 2. can also have per-posting cleared status 50 | hledgerdev -f- register --cleared 51 | <<< 52 | 2012/1/1 53 | a 1 54 | *b 2 55 | * c 4 56 | d 57 | >>> 58 | 2012/01/01 b 2 2 59 | c 4 6 60 | >>>= 0 61 | 62 | 63 | # 3. also works with balance as shown, same as ledger. Hmm. 64 | hledgerdev -f- balance --uncleared 65 | <<< 66 | 2012/1/1 67 | a 1 68 | *b 2 69 | d 70 | 71 | >>> 72 | 1 a 73 | -3 d 74 | -------------------- 75 | -2 76 | >>>=0 77 | -------------------------------------------------------------------------------- /CSV.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: hledger How to read CSV files 3 | --- 4 | 5 | # How to read CSV files 6 | 7 | Here's a quick example of [converting a CSV file](MANUAL.html#csv-files). 8 | 9 | Say we have downloaded `checking.csv` from a bank for the first time: 10 | 11 | "Date","Note","Amount" 12 | "2012/3/22","DEPOSIT","50.00" 13 | "2012/3/23","TRANSFER TO SAVINGS","-10.00" 14 | 15 | We could create `checking.csv.rules` containing: 16 | 17 | account1 assets:bank:checking 18 | skip 1 19 | fields date, description, amount 20 | currency $ 21 | 22 | if ~ SAVINGS 23 | account2 assets:bank:savings 24 | 25 | This says: 26 | "always use assets:bank:checking as the first account; 27 | ignore the first line; 28 | use the first, second and third CSV fields as the entry date, description and amount respectively; 29 | always prepend $ to the amount value; 30 | if the CSV record contains 'SAVINGS', use assets:bank:savings as the second account". 31 | [CSV files](MANUAL.html#csv-files) in the manual describes the syntax. 32 | 33 | Now hledger can read this CSV file: 34 | 35 | $ hledger -f checking.csv print 36 | using conversion rules file checking.csv.rules 37 | 2012/03/22 DEPOSIT 38 | income:unknown $-50.00 39 | assets:bank:checking $50.00 40 | 41 | 2012/03/23 TRANSFER TO SAVINGS 42 | assets:bank:savings $10.00 43 | assets:bank:checking $-10.00 44 | 45 | We might save this output as `checking.journal`, and/or merge it (manually) into the main journal file. 46 | 47 | -------------------------------------------------------------------------------- /hledger-web/Handler/RegisterR.hs: -------------------------------------------------------------------------------- 1 | -- | /register handlers. 2 | 3 | module Handler.RegisterR where 4 | 5 | import Import 6 | 7 | import Data.Maybe 8 | 9 | import Handler.Common 10 | import Handler.Post 11 | import Handler.Utils 12 | 13 | import Hledger.Query 14 | import Hledger.Reports 15 | import Hledger.Cli.Options 16 | import Hledger.Web.Options 17 | 18 | -- | The main journal/account register view, with accounts sidebar. 19 | getRegisterR :: Handler RepHtml 20 | getRegisterR = do 21 | vd@VD{..} <- getViewData 22 | staticRootUrl <- (staticRoot . settings) <$> getYesod 23 | let sidecontent = sidebar vd 24 | -- injournal = isNothing inacct 25 | filtering = m /= Any 26 | title = "Transactions in "++a++s1++s2 27 | where 28 | (a,inclsubs) = fromMaybe ("all accounts",False) $ inAccount qopts 29 | s1 = if inclsubs then " (and subaccounts)" else "" 30 | s2 = if filtering then ", filtered" else "" 31 | maincontent = registerReportHtml opts vd $ accountTransactionsReport (reportopts_ $ cliopts_ opts) j m $ fromMaybe Any $ inAccountQuery qopts 32 | defaultLayout $ do 33 | setTitle "hledger-web register" 34 | toWidget [hamlet| 35 | ^{topbar vd} 36 | 37 | 38 | ^{sidecontent} 39 | 40 | 41 | #{title} 42 | ^{searchform vd} 43 | ^{maincontent} 44 | ^{addform staticRootUrl vd} 45 | ^{editform vd} 46 | ^{importform} 47 | |] 48 | 49 | postRegisterR :: Handler RepHtml 50 | postRegisterR = handlePost 51 | -------------------------------------------------------------------------------- /tests/87-wrong-balance.test: -------------------------------------------------------------------------------- 1 | # 1. issue 87, hledger should give this balance. 2 | hledgerdev -f - balance --no-total b 3 | <<< 4 | 1/1 5 | a -553.653 X @@ 2609.92 6 | a -5.684 X @@ 26.10 7 | a -50.833 X @@ 234.90 8 | a -49.714 X @@ 234.90 9 | a -49.957 X @@ 234.90 10 | a -49.778 X @@ 234.90 11 | a -142.316 X @@ 674.01 12 | a -49.029 X @@ 234.90 13 | a -51.233 X @@ 234.90 14 | a -49.204 X @@ 234.90 15 | a -49.474 X @@ 234.90 16 | a -47.773 X @@ 234.90 17 | a -109.439 X @@ 576.96 18 | a -31.133 X @@ 171.51 19 | a -438.249 X @@ 2537.90 20 | a -11.927 X @@ 72.03 21 | a -170.721 X @@ 990.18 22 | a 1910.117 X @@ 10742.52 23 | b 24 | >>> 25 | -969.81 b 26 | >>>= 0 27 | 28 | # 2. As above, but the prices have a commodity - should work the same. 29 | hledgerdev -f - balance --no-total b 30 | <<< 31 | 1/1 32 | a -553.653 X @@ 2609.92 Y 33 | a -5.684 X @@ 26.10 Y 34 | a -50.833 X @@ 234.90 Y 35 | a -49.714 X @@ 234.90 Y 36 | a -49.957 X @@ 234.90 Y 37 | a -49.778 X @@ 234.90 Y 38 | a -142.316 X @@ 674.01 Y 39 | a -49.029 X @@ 234.90 Y 40 | a -51.233 X @@ 234.90 Y 41 | a -49.204 X @@ 234.90 Y 42 | a -49.474 X @@ 234.90 Y 43 | a -47.773 X @@ 234.90 Y 44 | a -109.439 X @@ 576.96 Y 45 | a -31.133 X @@ 171.51 Y 46 | a -438.249 X @@ 2537.90 Y 47 | a -11.927 X @@ 72.03 Y 48 | a -170.721 X @@ 990.18 Y 49 | a 1910.117 X @@ 10742.52 Y 50 | b 51 | >>> 52 | -969.81 Y b 53 | >>>= 0 54 | -------------------------------------------------------------------------------- /tests/register-depth.test: -------------------------------------------------------------------------------- 1 | # 1. register --depth N matches postings as usual but clips account names to N 2 | hledgerdev -f - register aa --depth 1 3 | <<< 4 | 2010/1/1 x 5 | a:aa:aaa 1 6 | b 7 | >>> 8 | 2010/01/01 x a 1 1 9 | >>>=0 10 | 11 | # 2. similar to above, postings with same clipped account name are not aggregated 12 | hledgerdev -f - register aa --depth 2 13 | <<< 14 | 2010/1/1 x 15 | a:aa 1 16 | b:bb:bbb 17 | 18 | 2010/1/1 y 19 | a:aa 1 20 | b:bb:bbb 21 | 22 | 2010/1/2 z 23 | a:aa 1 24 | b:bb:bbb 25 | >>> 26 | 2010/01/01 x a:aa 1 1 27 | 2010/01/01 y a:aa 1 2 28 | 2010/01/02 z a:aa 1 3 29 | >>>=0 30 | 31 | # 3. as above, but with a reporting interval causing postings to be aggregated 32 | hledgerdev -f - register aa --depth 1 --daily 33 | <<< 34 | 2010/1/1 x 35 | a:aa 1 36 | b:bb:bbb 37 | 38 | 2010/1/1 y 39 | a:aa 1 40 | b:bb:bbb 41 | 42 | 2010/1/2 z 43 | a:aa 1 44 | b:bb:bbb 45 | >>> 46 | 2010/01/01 - 2010/01/01 a 2 2 47 | 2010/01/02 - 2010/01/02 a 1 3 48 | >>>=0 49 | 50 | # 4. with --cleared 51 | hledgerdev -f - register a --depth 1 --cleared 52 | <<< 53 | 2012/1/1 * 54 | (a:aa) 1 55 | >>> 56 | 2012/01/01 (a) 1 1 57 | >>>2 58 | >>>=0 59 | -------------------------------------------------------------------------------- /hledger/Hledger/Cli/Balancesheet.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE QuasiQuotes, RecordWildCards, NoCPP #-} 2 | {-| 3 | 4 | The @balancesheet@ command prints a simple balance sheet. 5 | 6 | -} 7 | 8 | module Hledger.Cli.Balancesheet ( 9 | balancesheet 10 | ,tests_Hledger_Cli_Balancesheet 11 | ) where 12 | 13 | import qualified Data.Text.Lazy.IO as LT 14 | import Test.HUnit 15 | import Text.Shakespeare.Text 16 | 17 | import Hledger 18 | import Hledger.Cli.Options 19 | import Hledger.Cli.Balance 20 | 21 | 22 | -- | Print a simple balance sheet. 23 | balancesheet :: CliOpts -> Journal -> IO () 24 | balancesheet CliOpts{reportopts_=ropts} j = do 25 | -- let lines = case formatFromOpts ropts of Left err, Right ... 26 | d <- getCurrentDay 27 | let q = queryFromOpts d (withoutBeginDate ropts) 28 | assetreport@(_,assets) = accountsReport ropts (And [q, journalAssetAccountQuery j]) j 29 | liabilityreport@(_,liabilities) = accountsReport ropts (And [q, journalLiabilityAccountQuery j]) j 30 | total = assets + liabilities 31 | LT.putStr $ [lt|Balance Sheet 32 | 33 | Assets: 34 | #{unlines $ accountsReportAsText ropts assetreport} 35 | Liabilities: 36 | #{unlines $ accountsReportAsText ropts liabilityreport} 37 | 38 | Total: 39 | -------------------- 40 | #{padleft 20 $ showMixedAmountWithoutPrice total} 41 | |] 42 | 43 | withoutBeginDate :: ReportOpts -> ReportOpts 44 | withoutBeginDate ropts@ReportOpts{..} = ropts{begin_=Nothing, period_=p} 45 | where p = case period_ of Nothing -> Nothing 46 | Just (i, DateSpan _ e) -> Just (i, DateSpan Nothing e) 47 | 48 | tests_Hledger_Cli_Balancesheet = TestList 49 | [ 50 | ] 51 | -------------------------------------------------------------------------------- /ALIASES.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: hledger How to use account aliases 3 | --- 4 | 5 | # How to use account aliases 6 | 7 | Here's an example of using [account aliases](MANUAL.html#account-aliases). 8 | 9 | Say a sole proprietor has a personal.journal: 10 | 11 | 1/1 12 | expenses:food $1 13 | assets:cash 14 | 15 | and a business.journal: 16 | 17 | 1/1 18 | expenses:office supplies $1 19 | assets:business checking 20 | 21 | Here each entity has a simple journal with its own simple chart of 22 | accounts. But at tax reporting time, we need to view these as a single 23 | entity. So in unified.journal we adjust the personal account names to fit 24 | within the business chart of accounts: 25 | 26 | alias expenses = equity:draw:personal 27 | alias assets:cash = assets:personal cash 28 | include personal.journal 29 | end aliases 30 | include business.journal 31 | 32 | giving: 33 | 34 | $ hledger -f unified.journal print 35 | 2011/01/01 36 | equity:draw:personal:food $1 37 | assets:personal cash $-1 38 | 39 | 2011/01/01 40 | expenses:office supplies $1 41 | assets:business checking $-1 42 | 43 | You can also specify aliases on the command line. This could be useful to 44 | rewrite account names when sharing a report with someone else, such as 45 | your accountant: 46 | 47 | $ hledger --alias 'my earning=income:business' 48 | 49 | Command-line alias options are applied after any alias directives in the 50 | journal. At most one alias directive and one alias option will be applied 51 | to each account name. 52 | 53 | -------------------------------------------------------------------------------- /tests/timelog.test: -------------------------------------------------------------------------------- 1 | # a timelog session is parsed as a similarly-named transaction with one virtual posting 2 | hledgerdev -f - print 3 | <<< 4 | i 2009/1/1 08:00:00 something 5 | o 2009/1/1 09:00:00 6 | 7 | >>> 8 | 2009/01/01 * 08:00-09:00 9 | (something) 1.0h 10 | 11 | >>>2 12 | >>>= 0 13 | 14 | # ledger timelog example from #ledger 15 | # ==== consulting.timelog 16 | # ; Timelog for consulting sideline 17 | # ; All times UTC 18 | # i 2011/01/26 16:00:00 XXXX:Remote "IPMI Access" 19 | # o 2011/01/26 16:15:00 20 | 21 | # i 2011/01/26 17:45:00 XXXX:Onsite "Fix opty server" 22 | # o 2011/01/26 20:00:00 23 | 24 | # i 2011/01/27 18:00:00 XXXX:Remote "SSL certificate for www.YYYY.com" 25 | # o 2011/01/27 18:15:00 26 | 27 | # ; vim:ts=2 sw=2 sts=2 et ft=ledger fdm=manual: 28 | # ==== consulting.dat 29 | # ; Ledger for Consulting sideline 30 | 31 | # !account Consulting 32 | # !include consulting.timelog 33 | # !end 34 | 35 | 36 | # 2010/02/27 (INV#2) XXXX Test Invoice 37 | # Consulting:XXXX:Remote -0.5h @ $75.00 38 | # Consulting:XXXX:Onsite -2.25h @ $100.00 39 | # Receivable:Consulting:XXXX 40 | 41 | 42 | # ; vim:ts=2 sw=2 sts=2 et ft=ledger fdm=manual: 43 | # ==== command: ledger -f consulting.dat -s bal 44 | # -2.25h Consulting:XXXX:Onsite 45 | # 2.25h Consulting:XXXX:Onsite "Fix opty server" 46 | # -30.0m Consulting:XXXX:Remote 47 | # 15.0m Consulting:XXXX:Remote "IPMI Access" 48 | # 15.0m Consulting:XXXX:Remote "SSL certificate for www.YYYY.com" 49 | # $262.5 Receivable:Consulting:XXXX 50 | # -------------------- 51 | # $262.5 52 | -------------------------------------------------------------------------------- /extra/makefile: -------------------------------------------------------------------------------- 1 | # some hledger-related make scripts 2 | 3 | HLEDGER=hledger 4 | 5 | YEAR:=$(shell date +%Y) 6 | MONTH:=$(shell date +%m) 7 | MONTHS:=$(shell ghc -e "(putStr . unwords . map show) [1..$(MONTH)]") 8 | MONTHS2:=$(shell ghc -e "(putStr . unwords . map show) [2..$(MONTH)]") 9 | 10 | ############################################################################### 11 | 12 | # convert latest bank csv downloads to journal files 13 | BANKJOURNALS = \ 14 | mint.journal \ 15 | WellsFargoChecking1.journal \ 16 | WellsFargoSavings2.journal \ 17 | WellsFargoSavings3.journal \ 18 | WellsFargoCreditCard4.journal \ 19 | Paypal.journal 20 | convert-csv: move-csv $(BANKJOURNALS) 21 | 22 | # move and rename any newly downloaded bank csv files 23 | DOWNLOADDIR=~/Desktop 24 | move-csv: 25 | @(F=$(DOWNLOADDIR)/transactions.csv; [ -e $$F ] && (mv $$F mint.csv; echo new mint.csv found) || exit 0) 26 | @(F=$(DOWNLOADDIR)/Checking1.csv; [ -e $$F ] && (mv $$F WellsFargoChecking1.csv; echo new WellsFargoChecking1.csv found) || exit 0) 27 | @(F=$(DOWNLOADDIR)/Savings2.csv; [ -e $$F ] && (mv $$F WellsFargoSavings2.csv; echo new WellsFargoSavings2.csv found) || exit 0) 28 | @(F=$(DOWNLOADDIR)/Savings3.csv; [ -e $$F ] && (mv $$F WellsFargoSavings3.csv; echo new WellsFargoSavings3.csv found) || exit 0) 29 | @(F=$(DOWNLOADDIR)/CreditCard4.csv; [ -e $$F ] && (mv $$F WellsFargoCreditCard4.csv; echo new WellsFargoCreditCard4.csv found) || exit 0) 30 | @(F=$(DOWNLOADDIR)/Download.csv; [ -e $$F ] && (mv $$F Paypal.csv; echo new Paypal.csv found) || exit 0) 31 | 32 | # convert a csv to a journal using the similarly named rules file 33 | %.journal: %.csv %.rules 34 | $(HLEDGER) convert $< >$@ 35 | 36 | %.rules: 37 | touch '$@' 38 | 39 | -------------------------------------------------------------------------------- /hledger-web/Handler/JournalR.hs: -------------------------------------------------------------------------------- 1 | -- | /journal handlers. 2 | 3 | module Handler.JournalR where 4 | 5 | import Import 6 | 7 | import Handler.Common 8 | import Handler.Post 9 | import Handler.Utils 10 | 11 | import Hledger.Query 12 | import Hledger.Reports 13 | import Hledger.Cli.Options 14 | import Hledger.Web.Options 15 | 16 | -- | The formatted journal view, with sidebar. 17 | getJournalR :: Handler RepHtml 18 | getJournalR = do 19 | vd@VD{..} <- getViewData 20 | staticRootUrl <- (staticRoot . settings) <$> getYesod 21 | let sidecontent = sidebar vd 22 | -- XXX like registerReportAsHtml 23 | inacct = inAccount qopts 24 | -- injournal = isNothing inacct 25 | filtering = m /= Any 26 | -- showlastcolumn = if injournal && not filtering then False else True 27 | title = case inacct of 28 | Nothing -> "Journal"++s2 29 | Just (a,inclsubs) -> "Transactions in "++a++s1++s2 30 | where s1 = if inclsubs then " (and subaccounts)" else "" 31 | where 32 | s2 = if filtering then ", filtered" else "" 33 | maincontent = journalTransactionsReportAsHtml opts vd $ journalTransactionsReport (reportopts_ $ cliopts_ opts) j m 34 | defaultLayout $ do 35 | setTitle "hledger-web journal" 36 | toWidget [hamlet| 37 | ^{topbar vd} 38 | 39 | 40 | ^{sidecontent} 41 | 42 | 43 | #{title} 44 | ^{searchform vd} 45 | ^{maincontent} 46 | ^{addform staticRootUrl vd} 47 | ^{editform vd} 48 | ^{importform} 49 | |] 50 | 51 | postJournalR :: Handler RepHtml 52 | postJournalR = handlePost 53 | 54 | -------------------------------------------------------------------------------- /tests/comments.test: -------------------------------------------------------------------------------- 1 | # comment tests 2 | 3 | # 1. 4 | hledgerdev -f - print 5 | <<< 6 | 2009/01/01 x 7 | ; transaction comment 1 8 | ; transaction comment 2 9 | a 1 10 | b 11 | >>> 12 | 2009/01/01 x 13 | ; transaction comment 1 14 | ; transaction comment 2 15 | a 1 16 | b -1 17 | 18 | >>>=0 19 | 20 | # 2. 21 | hledgerdev -f - print 22 | <<< 23 | 2009/01/01 x 24 | a 1 25 | b 26 | ; comment line after postings 27 | >>> 28 | 2009/01/01 x 29 | a 1 30 | b -1 31 | 32 | >>>=0 33 | 34 | # 3. print should preserve comments 35 | hledgerdev -f - print 36 | <<< 37 | ; isolated journal comment 38 | 39 | ; pre-transaction journal comment 40 | 2009/1/1 x ; transaction comment 41 | a 1 ; posting 1 comment 42 | ; posting 1 comment 2 43 | b 44 | ; posting 2 comment 45 | ; post-transaction journal comment 46 | >>> 47 | 2009/01/01 x ; transaction comment 48 | a 1 49 | ; posting 1 comment 50 | ; posting 1 comment 2 51 | b -1 ; posting 2 comment 52 | 53 | >>>2 54 | >>>=0 55 | 56 | # 4. a posting comment should appear in print 57 | hledgerdev -f - print 58 | <<< 59 | 2010/01/01 x 60 | a 1 ; comment 61 | b -1 62 | 63 | >>> 64 | 2010/01/01 x 65 | a 1 ; comment 66 | b -1 67 | 68 | >>>2 69 | >>>=0 70 | 71 | # 5. a posting comment should not appear in register 72 | hledgerdev -f - register 73 | <<< 74 | 2010/1/1 x 75 | a 1 ; comment 76 | b 77 | 78 | >>> 79 | 2010/01/01 x a 1 1 80 | b -1 0 81 | >>>2 82 | >>>=0 83 | -------------------------------------------------------------------------------- /site/site.hs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env runhaskell 2 | {-# LANGUAGE OverloadedStrings #-} 3 | import Control.Applicative ((<$>)) 4 | import Data.Monoid (mappend) 5 | import Hakyll 6 | 7 | import Control.Monad 8 | import Data.List 9 | import System.Directory 10 | import System.Process 11 | import Text.Pandoc.Options 12 | import Text.Printf 13 | 14 | main = do 15 | symlinkPagesFromParentDir 16 | symlinkIndexHtml 17 | symlinkProfsDir 18 | hakyll $ do 19 | match ("images/*" .||. "js/**" .||. "robots.txt") $ do 20 | route idRoute 21 | compile copyFileCompiler 22 | match "css/*" $ do 23 | route idRoute 24 | compile compressCssCompiler 25 | match "templates/*" $ compile templateCompiler 26 | match ("*.md" .||. "0.20/*.md" .||. "0.19/*.md" .||. "0.18/*.md") $ do 27 | route $ setExtension "html" 28 | compile $ 29 | pandocCompilerWith 30 | def 31 | def{writerTableOfContents=True 32 | ,writerTOCDepth=4 33 | ,writerStandalone=True 34 | ,writerTemplate="
$toc$
\n$body$" 35 | } 36 | >>= loadAndApplyTemplate "templates/default.html" defaultContext 37 | >>= relativizeUrls 38 | 39 | symlinkPagesFromParentDir = do 40 | fs <- filter (".md" `isSuffixOf`) `fmap` getDirectoryContents ".." 41 | forM_ fs $ \f -> system $ printf "[ -f %s ] || ln -s ../%s" f f 42 | 43 | symlinkIndexHtml = ensureSiteDir >> system "ln -sf README.html _site/index.html" 44 | 45 | symlinkProfsDir = ensureSiteDir >> system "ln -sf ../../profs _site/profs" 46 | 47 | ensureSiteDir = system "mkdir -p _site" 48 | 49 | 50 | -------------------------------------------------------------------------------- /tests/parse-ledger-sample.test: -------------------------------------------------------------------------------- 1 | # can we parse this sample journal from c++ ledger source 2 | hledgerdev -f- stats 3 | <<< 4 | ; -*- ledger -*- 5 | 6 | N $ 7 | 8 | = /^Expenses:Books/ 9 | (Liabilities:Taxes) -0.10 10 | 11 | ~ Monthly 12 | Assets:Bank:Checking $500.00 13 | Income:Salary 14 | 15 | ~ Yearly 16 | Expenses:Donations $100.00 17 | Assets:Bank:Checking 18 | 19 | 2004/05/01 * Checking balance 20 | Assets:Bank:Checking $1,000.00 21 | Equity:Opening Balances 22 | 23 | 2004/05/03=2004/05/01 * Investment balance 24 | Assets:Brokerage 50 AAPL @ $30.00 25 | Equity:Opening Balances 26 | 27 | 2004/05/14 * Páy dày 28 | Assets:Bank:Checking 500.00€ 29 | Income:Salary 30 | 31 | 2004/05/14 * Another dày in which there is Páying 32 | Asséts:Bánk:Chécking:Asséts:Bánk:Chécking $500.00 33 | Income:Salary 34 | 35 | 2004/05/14 * Another dày in which there is Páying 36 | Русский язык:Активы:Русский язык:Русский язык $1000.00 37 | Income:Salary 38 | 39 | tag foo 40 | 41 | 2004/05/27 Book Store 42 | Expenses:Books $20.00 43 | Expenses:Cards $40.00 44 | Expenses:Docs $30.00 45 | Liabilities:MasterCard 46 | 47 | end tag 48 | 49 | 2004/05/27 (100) Credit card company 50 | ; This is an xact note! 51 | ; Sample: Value 52 | Liabilities:MasterCard $20.00 53 | ; This is a posting note! 54 | ; Sample: Another Value 55 | ; :MyTag: 56 | Assets:Bank:Checking 57 | ; :AnotherTag: 58 | >>>= 0 59 | -------------------------------------------------------------------------------- /SCREENSHOTS.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: hledger screenshots 3 | --- 4 | 5 | # Screenshots 6 | 7 | 8 | Click to enlarge, or mouse over for captions.. 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /tests/default-commodity.test: -------------------------------------------------------------------------------- 1 | # a default commodity defined with the D directive will be used for any 2 | # commodity-less amounts in subsequent transactions. 3 | 4 | # 1. no default commodity 5 | hledgerdev -f- print 6 | <<< 7 | 2010/1/1 8 | a 1000 9 | b 10 | >>> 11 | 2010/01/01 12 | a 1000 13 | b -1000 14 | 15 | >>>=0 16 | 17 | # 2. pound, two decimal places, no digit group separator 18 | hledgerdev -f- print 19 | <<< 20 | D £1000.00 21 | 2010/1/1 22 | a 1000 23 | b 24 | >>> 25 | 2010/01/01 26 | a £1000.00 27 | b £-1000.00 28 | 29 | >>>=0 30 | 31 | # 3. dollar, comma decimal point, three decimal places, no digit group separator 32 | hledgerdev -f- print 33 | <<< 34 | D $1,000 35 | 2010/1/1 36 | a 1000 37 | b 38 | >>> 39 | 2010/01/01 40 | a $1000,000 41 | b $-1000,000 42 | 43 | >>>=0 44 | 45 | # 4. dollar, three digit group separator, one decimal place 46 | hledgerdev -f- print 47 | <<< 48 | D $1,000.0 49 | 2010/1/1 50 | (a) 1000000 51 | >>> 52 | 2010/01/01 53 | (a) $1,000,000.0 54 | 55 | >>>=0 56 | 57 | # 5. as above, sets the commodity of the commodityless amount, but an 58 | # earlier explicit dollar amount sets the display settings for dollar 59 | hledgerdev -f- print 60 | <<< 61 | D $1,000.0 62 | 2010/1/1 63 | (a) $1000000.00 64 | (b) 1000000 65 | >>> 66 | 2010/01/01 67 | (a) $1000000.00 68 | (b) $1000000.00 69 | 70 | >>>=0 71 | 72 | # 6. as above, but the commodityless amount is earliest, so it sets the 73 | # display settings for dollar. The greatest precision is preserved though. 74 | hledgerdev -f- print 75 | <<< 76 | D $1,000.0 77 | 2010/1/1 78 | (a) 1000000 79 | (b) $1000000.00 80 | >>> 81 | 2010/01/01 82 | (a) $1,000,000.00 83 | (b) $1,000,000.00 84 | 85 | >>>=0 86 | 87 | -------------------------------------------------------------------------------- /hledger-lib/Hledger/Data.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | 3 | The Hledger.Data library allows parsing and querying of C++ ledger-style 4 | journal files. It generally provides a compatible subset of C++ ledger's 5 | functionality. This package re-exports all the Hledger.Data.* modules 6 | (except UTF8, which requires an explicit import.) 7 | 8 | -} 9 | 10 | module Hledger.Data ( 11 | module Hledger.Data.Account, 12 | module Hledger.Data.AccountName, 13 | module Hledger.Data.Amount, 14 | module Hledger.Data.Commodity, 15 | module Hledger.Data.Dates, 16 | module Hledger.Data.Journal, 17 | module Hledger.Data.Ledger, 18 | module Hledger.Data.Posting, 19 | module Hledger.Data.TimeLog, 20 | module Hledger.Data.Transaction, 21 | module Hledger.Data.Types, 22 | tests_Hledger_Data 23 | ) 24 | where 25 | import Test.HUnit 26 | 27 | import Hledger.Data.Account 28 | import Hledger.Data.AccountName 29 | import Hledger.Data.Amount 30 | import Hledger.Data.Commodity 31 | import Hledger.Data.Dates 32 | import Hledger.Data.Journal 33 | import Hledger.Data.Ledger 34 | import Hledger.Data.Posting 35 | import Hledger.Data.TimeLog 36 | import Hledger.Data.Transaction 37 | import Hledger.Data.Types 38 | 39 | tests_Hledger_Data = TestList 40 | [ 41 | tests_Hledger_Data_Account 42 | ,tests_Hledger_Data_AccountName 43 | ,tests_Hledger_Data_Amount 44 | ,tests_Hledger_Data_Commodity 45 | ,tests_Hledger_Data_Dates 46 | ,tests_Hledger_Data_Journal 47 | ,tests_Hledger_Data_Ledger 48 | ,tests_Hledger_Data_Posting 49 | ,tests_Hledger_Data_TimeLog 50 | ,tests_Hledger_Data_Transaction 51 | -- ,tests_Hledger_Data_Types 52 | ] 53 | -------------------------------------------------------------------------------- /hledger/Hledger/Cli/Histogram.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | 3 | Print a histogram report. 4 | 5 | -} 6 | 7 | module Hledger.Cli.Histogram 8 | where 9 | import Data.List 10 | import Data.Maybe 11 | import Data.Ord 12 | import Text.Printf 13 | 14 | import Hledger.Cli.Options 15 | import Hledger.Data 16 | import Hledger.Reports 17 | import Hledger.Query 18 | import Prelude hiding (putStr) 19 | import Hledger.Utils.UTF8IOCompat (putStr) 20 | 21 | 22 | barchar = '*' 23 | 24 | -- | Print a histogram of some statistic per reporting interval, such as 25 | -- number of postings per day. 26 | histogram :: CliOpts -> Journal -> IO () 27 | histogram CliOpts{reportopts_=ropts} j = do 28 | d <- getCurrentDay 29 | putStr $ showHistogram ropts (queryFromOpts d ropts) j 30 | 31 | showHistogram :: ReportOpts -> Query -> Journal -> String 32 | showHistogram opts q j = concatMap (printDayWith countBar) spanps 33 | where 34 | i = intervalFromOpts opts 35 | interval | i == NoInterval = Days 1 36 | | otherwise = i 37 | span = queryDateSpan (date2_ opts) q `orDatesFrom` journalDateSpan j 38 | spans = filter (DateSpan Nothing Nothing /=) $ splitSpan interval span 39 | spanps = [(s, filter (isPostingInDateSpan s) ps) | s <- spans] 40 | -- same as Register 41 | -- should count transactions, not postings ? 42 | -- ps = sortBy (comparing postingDate) $ filterempties $ filter matchapats $ filterdepth $ journalPostings j 43 | ps = sortBy (comparing postingDate) $ filterempties $ filter (q `matchesPosting`) $ journalPostings j 44 | filterempties 45 | | queryEmpty q = id 46 | | otherwise = filter (not . isZeroMixedAmount . pamount) 47 | 48 | printDayWith f (DateSpan b _, ts) = printf "%s %s\n" (show $ fromJust b) (f ts) 49 | 50 | countBar ps = replicate (length ps) barchar 51 | -------------------------------------------------------------------------------- /ANNOUNCE: -------------------------------------------------------------------------------- 1 | I'm pleased to announce hledger and hledger-web 0.21! 2 | 3 | hledger is a command-line tool and haskell library for tracking 4 | financial transactions, which are stored in a human-readable plain 5 | text format. In addition to reporting, it can also help you record new 6 | transactions, or convert CSV data from your bank. Add-on packages 7 | include hledger-web (providing a web interface), hledger-irr and hledger-interest. 8 | 9 | hledger is inspired by and compatible with John Wiegley's Ledger. For 10 | more, see http://hledger.org . 11 | 12 | Install it: 13 | 14 | cabal update; cabal install hledger [hledger-web] 15 | 16 | For more installation help, see http://hledger.org/MANUAL.html#installing . 17 | Or, sponsor a ready-to-run binary for your platform: http://hledger.org/DOWNLOAD.html . 18 | 19 | Release notes (http://hledger.org/NEWS.html#hledger-0.21): 20 | 21 | **Bugs fixed:** 22 | 23 | - parsing: don't fail when a csv amount has trailing whitespace (fixes #113) 24 | - web: don't show prices in the accounts sidebar (fixes #114) 25 | - web: show one line per commodity in charts. Needs more polish, but fixes #109. 26 | - web: bump yesod-platform dependency to avoid a cabal install failure 27 | 28 | **Journal reading:** 29 | 30 | - balance assertions are now checked after reading a journal 31 | 32 | **web command:** 33 | 34 | - web: support/require yesod 1.2 35 | - web: show zero-balance accounts in the sidebar (fixes #106) 36 | - web: use nicer select2 autocomplete widgets in the add form 37 | 38 | **Documentation and infrastructure:** 39 | 40 | - add basic cabal test suites for hledger-lib and hledger 41 | 42 | 43 | Release contributors: 44 | 45 | - Xinruo Sun enhanced the hledger-web add form 46 | - Clint Adams added cabal test suites 47 | - Jeff Richards did hledger-web cleanup 48 | - Peter Simons provided the build bot 49 | -------------------------------------------------------------------------------- /hledger-web/templates/homepage.hamlet: -------------------------------------------------------------------------------- 1 |

_{MsgHello} 2 | 3 |
    4 |
  1. Now that you have a working project you should use the # 5 | \Yesod book to learn more. # 6 | You can also use this scaffolded site to explore some basic concepts. 7 | 8 |
  2. This page was generated by the #{handlerName} handler in # 9 | \Handler/Home.hs. 10 | 11 |
  3. The #{handlerName} handler is set to generate your site's home screen in Routes file # 12 | config/routes 13 | 14 |
  4. The HTML you are seeing now is actually composed by a number of widgets, # 15 | most of them are brought together by the defaultLayout function which # 16 | is defined in the Foundation.hs module, and used by #{handlerName}. # 17 | All the files for templates and wigdets are in templates. 18 | 19 |
  5. 20 | A Widget's Html, Css and Javascript are separated in three files with the # 21 | \.hamlet, .lucius and .julius extensions. 22 | 23 |
  6. If you had javascript enabled then you wouldn't be seeing this. 24 | 25 |
  7. 26 | This is an example trivial Form. Read the # 27 | \Forms chapter # 28 | on the yesod book to learn more about them. 29 | $maybe (info,con) <- submission 30 |
    31 | Your file's type was #{fileContentType info}. You say it has: #{con} 32 |
    33 | ^{formWidget} 34 | 35 | 36 |
  8. And last but not least, Testing. In tests/main.hs you will find a # 37 | test suite that performs tests on this page. # 38 | You can run your tests by doing:
    yesod test
    39 | -------------------------------------------------------------------------------- /hledger-web/static/jquery.url.js: -------------------------------------------------------------------------------- 1 | jQuery.url=function(){var segments={};var parsed={};var options={url:window.location,strictMode:false,key:["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],q:{name:"queryKey",parser:/(?:^|&)([^&=]*)=?([^&]*)/g},parser:{strict:/^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,loose:/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/}};var parseUri=function(){str=decodeURI(options.url);var m=options.parser[options.strictMode?"strict":"loose"].exec(str);var uri={};var i=14;while(i--){uri[options.key[i]]=m[i]||""}uri[options.q.name]={};uri[options.key[12]].replace(options.q.parser,function($0,$1,$2){if($1){uri[options.q.name][$1]=$2}});return uri};var key=function(key){if(!parsed.length){setUp()}if(key=="base"){if(parsed.port!==null&&parsed.port!==""){return parsed.protocol+"://"+parsed.host+":"+parsed.port+"/"}else{return parsed.protocol+"://"+parsed.host+"/"}}return(parsed[key]==="")?null:parsed[key]};var param=function(item){if(!parsed.length){setUp()}return(parsed.queryKey[item]===null)?null:parsed.queryKey[item]};var setUp=function(){parsed=parseUri();getSegments()};var getSegments=function(){var p=parsed.path;segments=[];segments=parsed.path.length==1?{}:(p.charAt(p.length-1)=="/"?p.substring(1,p.length-1):path=p.substring(1)).split("/")};return{setMode:function(mode){strictMode=mode=="strict"?true:false;return this},setUrl:function(newUri){options.url=newUri===undefined?window.location:newUri;setUp();return this},segment:function(pos){if(!parsed.length){setUp()}if(pos===undefined){return segments.length}return(segments[pos]===""||segments[pos]===undefined)?null:segments[pos]},attr:key,param:param}}(); -------------------------------------------------------------------------------- /hledger/Hledger/Cli/Tests.hs: -------------------------------------------------------------------------------- 1 | -- {-# OPTIONS_GHC -F -pgmF htfpp #-} 2 | {-# LANGUAGE CPP #-} 3 | {- | 4 | 5 | A simple test runner for hledger's built-in unit tests. 6 | 7 | -} 8 | 9 | module Hledger.Cli.Tests 10 | where 11 | import Control.Monad 12 | import System.Exit 13 | import Test.HUnit 14 | 15 | import Hledger 16 | import Hledger.Cli 17 | 18 | #ifdef TESTS 19 | 20 | import Test.Framework 21 | import {-@ HTF_TESTS @-} Hledger.Read.JournalReader 22 | 23 | -- | Run HTF unit tests and exit with success or failure. 24 | test' :: CliOpts -> IO () 25 | test' _opts = htfMain htf_importedTests 26 | 27 | #else 28 | 29 | -- | Run HUnit unit tests and exit with success or failure. 30 | test' :: CliOpts -> IO () 31 | test' opts = do 32 | results <- runTests opts 33 | if errors results > 0 || failures results > 0 34 | then exitFailure 35 | else exitWith ExitSuccess 36 | 37 | -- | Run all or just the matched unit tests and return their HUnit result counts. 38 | runTests :: CliOpts -> IO Counts 39 | runTests = liftM (fst . flip (,) 0) . runTestTT . flatTests 40 | 41 | -- | Run all or just the matched unit tests until the first failure or 42 | -- error, returning the name of the problem test if any. 43 | runTestsTillFailure :: CliOpts -> IO (Maybe String) 44 | runTestsTillFailure _ = undefined -- do 45 | -- let ts = flatTests opts 46 | -- results = liftM (fst . flip (,) 0) $ runTestTT $ 47 | -- firstproblem = find (\counts -> ) 48 | 49 | -- | All or pattern-matched tests, as a flat list to show simple names. 50 | flatTests opts = TestList $ filter (matchesAccount (queryFromOpts nulldate $ reportopts_ opts) . testName) $ flattenTests tests_Hledger_Cli 51 | 52 | -- | All or pattern-matched tests, in the original suites to show hierarchical names. 53 | hierarchicalTests opts = filterTests (matchesAccount (queryFromOpts nulldate $ reportopts_ opts) . testName) tests_Hledger_Cli 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /tools/generatejournal.hs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env runhaskell 2 | {- 3 | generateledger.hs NUMTXNS NUMACCTS ACCTDEPTH 4 | 5 | Outputs a dummy ledger file with the specified number of transactions, 6 | number of accounts, and account tree depth. Useful for 7 | testing/profiling/benchmarking. 8 | 9 | -} 10 | 11 | module Main 12 | where 13 | import System.Environment 14 | import Control.Monad 15 | import Data.Time.LocalTime 16 | import Data.Time.Calendar 17 | import Text.Printf 18 | import Numeric 19 | 20 | main = do 21 | args <- getArgs 22 | let [numtxns, numaccts, acctdepth] = map read args :: [Int] 23 | today <- getCurrentDay 24 | let (year,_,_) = toGregorian today 25 | let d = fromGregorian (year-1) 1 1 26 | let dates = iterate (addDays 1) d 27 | let accts = pair $ cycle $ take numaccts $ uniqueacctnames acctdepth 28 | mapM_ (\(n,d,(a,b)) -> putStr $ showtxn n d a b) $ take numtxns $ zip3 [1..] dates accts 29 | return () 30 | 31 | showtxn :: Int -> Day -> String -> String -> String 32 | showtxn txnno date acct1 acct2 = 33 | printf "%s transaction %d\n %-40s %2d\n %-40s %2d\n\n" d txnno acct1 amt acct2 (-amt) 34 | where 35 | d = show date 36 | amt = 1::Int 37 | 38 | uniqueacctnames :: Int -> [String] 39 | uniqueacctnames depth = uniqueacctnames' depth uniquenames 40 | where uniquenames = map hex [1..] where hex = flip showHex "" 41 | 42 | uniqueacctnames' depth uniquenames = group some ++ uniqueacctnames' depth rest 43 | where (some, rest) = splitAt depth uniquenames 44 | 45 | -- group ["a", "b", "c"] = ["a","a:b","a:b:c"] 46 | group :: [String] -> [String] 47 | group [] = [] 48 | group (a:as) = a : map ((a++":")++) (group as) 49 | 50 | pair :: [a] -> [(a,a)] 51 | pair [] = [] 52 | pair [a] = [(a,a)] 53 | pair (a:b:rest) = (a,b):pair rest 54 | 55 | getCurrentDay :: IO Day 56 | getCurrentDay = do 57 | t <- getZonedTime 58 | return $ localDay (zonedTimeToLocalTime t) 59 | 60 | -------------------------------------------------------------------------------- /tests/register-intervals.test: -------------------------------------------------------------------------------- 1 | # 1. monthly reporting interval, no end dates, shows just the intervals with data: 2 | hledgerdev -f- register --period 'monthly' 3 | <<< 4 | 2011/2/1 5 | (a) 1 6 | >>> 7 | 2011/02/01 - 2011/02/28 a 1 1 8 | >>>=0 9 | 10 | # 2. or with a query pattern, just the intervals with matched data: 11 | hledgerdev -f- register --period 'monthly' b 12 | <<< 13 | 2011/1/1 14 | (a) 1 15 | 16 | 2011/2/1 17 | (b) 1 18 | >>> 19 | 2011/02/01 - 2011/02/28 b 1 1 20 | >>>=0 21 | 22 | # 3. with --empty, show all intervals spanned by the journal 23 | # (unlike current ledger, but more useful) 24 | hledgerdev -f- register --period 'monthly' b --empty 25 | <<< 26 | 2011/1/1 27 | (a) 1 28 | 29 | 2011/2/1 30 | (b) 1 31 | 32 | 2011/3/1 33 | (c) 1 34 | >>> 35 | 2011/01/01 - 2011/01/31 0 0 36 | 2011/02/01 - 2011/02/28 b 1 1 37 | 2011/03/01 - 2011/03/31 0 1 38 | >>>=0 39 | 40 | # 4. any specified begin/end dates limit the intervals reported 41 | hledgerdev -f- register --period 'monthly to 2011/3/1' b --empty 42 | <<< 43 | 2011/1/1 44 | (a) 1 45 | 46 | 2011/2/1 47 | (b) 1 48 | 49 | 2011/3/1 50 | (c) 1 51 | >>> 52 | 2011/01/01 - 2011/01/31 0 0 53 | 2011/02/01 - 2011/02/28 b 1 1 54 | >>>=0 55 | 56 | # 5. likewise for date-restricting display expressions 57 | hledgerdev -f- register --period 'monthly to 2011/2/1' b --empty --display 'd<[2011/2/1]' 58 | <<< 59 | 2011/1/1 60 | (a) 1 61 | 62 | 2011/2/1 63 | (b) 1 64 | 65 | 2011/3/1 66 | (c) 1 67 | >>> 68 | 2011/01/01 - 2011/01/31 0 0 69 | >>>=0 70 | 71 | -------------------------------------------------------------------------------- /hledger/Hledger/Cli/Cashflow.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE QuasiQuotes, RecordWildCards, NoCPP #-} 2 | {-| 3 | 4 | The @cashflow@ command prints a simplified cashflow statement. It just 5 | shows the change in all "cash" accounts for the period (without the 6 | traditional segmentation into operating, investing, and financing 7 | cash flows.) 8 | 9 | -} 10 | 11 | module Hledger.Cli.Cashflow ( 12 | cashflow 13 | ,tests_Hledger_Cli_Cashflow 14 | ) where 15 | 16 | import qualified Data.Text.Lazy.IO as LT 17 | import Test.HUnit 18 | import Text.Shakespeare.Text 19 | 20 | import Hledger 21 | import Hledger.Cli.Options 22 | import Hledger.Cli.Balance 23 | 24 | 25 | -- | Print a simple cashflow statement. 26 | cashflow :: CliOpts -> Journal -> IO () 27 | cashflow CliOpts{reportopts_=ropts} j = do 28 | -- let lines = case formatFromOpts ropts of Left err, Right ... 29 | d <- getCurrentDay 30 | let q = queryFromOpts d (withoutBeginDate ropts) 31 | cashreport@(_,total) = accountsReport ropts (And [q, journalCashAccountQuery j]) j 32 | -- operatingreport@(_,operating) = accountsReport ropts (And [q, journalOperatingAccountMatcher j]) j 33 | -- investingreport@(_,investing) = accountsReport ropts (And [q, journalInvestingAccountMatcher j]) j 34 | -- financingreport@(_,financing) = accountsReport ropts (And [q, journalFinancingAccountMatcher j]) j 35 | -- total = operating + investing + financing 36 | LT.putStr $ [lt|Cashflow Statement 37 | 38 | Cash flows: 39 | #{unlines $ accountsReportAsText ropts cashreport} 40 | 41 | Total: 42 | -------------------- 43 | #{padleft 20 $ showMixedAmountWithoutPrice total} 44 | |] 45 | 46 | withoutBeginDate :: ReportOpts -> ReportOpts 47 | withoutBeginDate ropts@ReportOpts{..} = ropts{begin_=Nothing, period_=p} 48 | where p = case period_ of Nothing -> Nothing 49 | Just (i, DateSpan _ e) -> Just (i, DateSpan Nothing e) 50 | 51 | tests_Hledger_Cli_Cashflow = TestList 52 | [ 53 | ] 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: hledger 3 | --- 4 | 5 | # hledger 6 | 7 | hledger is a computer program for easily tracking money, time, or other 8 | commodities, inspired by and compatible with [ledger](http://ledger-cli.org). It is quite 9 | limited in features, but lightweight and reliable. For some, it is a 10 | bare-bones, simpler, less expensive alternative to Quicken or GnuCash. It 11 | is available for free under the GNU General Public License. 12 | 13 | hledger aims to help both computer experts and regular folks gain clarity 14 | in their finances and time management, but currently it is a little more 15 | suited to techies. I use it every day to: 16 | 17 | - track spending and income 18 | - see time reports by day/week/month/project 19 | - get accurate numbers for client billing and tax filing 20 | - track invoices 21 | 22 | hledger is first a command-line tool, but also provides a web interface 23 | (try the [demo](http://demo.hledger.org)). Read the [docs](DOCS.html) to 24 | learn more, including 25 | [how we are different from ledger](FAQ.html#how-does-hledger-relate-to-ledger), 26 | and get started tracking your numbers! 27 | 28 | **Community & support** 29 | 30 | **IRC (chat):** [irc.freenode.net/#ledger](irc://irc.freenode.net/#ledger) (shared with ledger) 31 | **Mail list:** [hledger.org/list](http://hledger.org/list) (for broader topics, you can also use [ledger's list](http://list.ledger-cli.org)) 32 | **Bug tracker:** [hledger.org/bugs](http://hledger.org/bugs) 33 | **Wishlist/planning:** [hledger.org/trello](http://hledger.org/trello) 34 | **Code:** [hledger.org/code](http://hledger.org/code) 35 | **Docs:** [Installation Guide](INSTALL.html), **[User Manual](MANUAL.html)**, [Release Notes](NEWS.html) and [more](DOCS.html) 36 | **Blog:** [joyful.com/blog](http://joyful.com/blog). 37 | 38 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /HCAR.tex: -------------------------------------------------------------------------------- 1 | % hledger-Sh.tex 2 | \begin{hcarentry}[updated]{hledger} 3 | \label{hledger} 4 | \report{Simon Michael}%11/11 5 | \status{ongoing development; suitable for daily use} 6 | \makeheader 7 | 8 | hledger is a library and end-user tool (with command-line, curses and web 9 | interfaces) for converting, recording, and analyzing financial 10 | transactions, using a simple human-editable plain text file format. It is 11 | a haskell port and friendly fork of John Wiegley's Ledger, licensed under 12 | GNU GPLv3+. 13 | 14 | hledger aims to be a reliable, practical tool for daily use. It reports 15 | charts of accounts or account balances, filters transactions by type, 16 | helps you record new transactions, converts CSV data from your bank, 17 | publishes your text journal with a rich web interface, generates simple 18 | charts, and provides an API for use in your own financial scripts and 19 | apps. 20 | 21 | In the last six months there have been two major releases. 0.15 focussed 22 | on features and 0.16 focussed on quality. Changes include: 23 | 24 | - new modal command-line interface, extensible with hledger-* executables in the path 25 | - more useful web interface, with real account registers and basic charts 26 | - hledger-web no longer needs to create support files, and uses latest yesod & warp 27 | - more ledger compatibility 28 | - misc command enhancements, API improvements, bug fixes, documentation updates 29 | - lines of code increased by 3k to 8k 30 | - project committers increased by 6 to 21 31 | 32 | Current plans include: 33 | 34 | - Continue the release rhythm of odd-numbered = features, even-numbered = 35 | quality/stability/polish, and releasing on the first of a month 36 | 37 | - In 0.17, clean up the storage layer, allow rcs integration via 38 | filestore, and read (or convert) more formats 39 | 40 | - Keep working towards wider usefulness, improving the web interface and 41 | providing standard financial reports 42 | 43 | \FurtherReading 44 | \url{http://hledger.org} 45 | \end{hcarentry} 46 | -------------------------------------------------------------------------------- /tests/read-csv.test: -------------------------------------------------------------------------------- 1 | # 1. read CSV to hledger journal format 2 | rm -rf t.rules$$; printf 'fields date, description, amount\ndate-format %%d/%%Y/%%m\ncurrency $\naccount1 assets:myacct\n' >t.rules$$; echo '10/2009/09,Flubber Co,50' | hledgerdev -f- print --rules-file t.rules$$; rm -rf t.rules$$ 3 | >>> 4 | 2009/09/10 Flubber Co 5 | income:unknown $-50 6 | assets:myacct $50 7 | 8 | >>>2 /using conversion rules file.*t.rules/ 9 | >>>=0 10 | 11 | # 2. reading CSV with in-field and out-field 12 | printf 'account1 Assets:MyAccount\ndate %%1\ndate-format %%d/%%Y/%%m\ndescription %%2\namount-in %%3\namount-out %%4\ncurrency $\n' >$$.rules ; hledgerdev -f- print --rules-file $$.rules; rm -rf $$.rules 13 | <<< 14 | 10/2009/09,Flubber Co,50, 15 | 11/2009/09,Flubber Co,,50 16 | >>> 17 | 2009/09/10 Flubber Co 18 | income:unknown $-50 19 | Assets:MyAccount $50 20 | 21 | 2009/09/11 Flubber Co 22 | expenses:unknown $50 23 | Assets:MyAccount $-50 24 | 25 | >>>2 /using conversion rules file.*[0-9]+\.rules/ 26 | >>>=0 27 | 28 | # 3. report rules parse error 29 | # rm -rf t.rules$$; printf 'date-fiel 0\ndate-format %%d/%%Y/%%m\ndescription-field 1\namount-field 2\ncurrency $\nbase-account assets:myacct\n' >t.rules$$; echo '10/2009/09,Flubber Co,50' | hledgerdev convert --rules-file t.rules$$; rm -rf t.rules$$ 30 | # >>> 31 | # 2009/09/10 Flubber Co 32 | # income:unknown $-50 33 | # assets:myacct $50 34 | 35 | # >>>2 /using conversion rules file.*t.rules/ 36 | # >>>=0 37 | 38 | # 4. handle conditions assigning multiple fields 39 | rm -rf t.rules$$; printf 'fields date, description, amount\ndate-format %%d/%%Y/%%m\ncurrency $\naccount1 assets:myacct\nif Flubber\n account2 acct\n comment cmt' >t.rules$$; echo '10/2009/09,Flubber Co,50' | hledgerdev -f- print --rules-file t.rules$$; rm -rf t.rules$$ 40 | >>> 41 | 2009/09/10 Flubber Co ;cmt 42 | acct $-50 43 | assets:myacct $50 44 | 45 | >>>2 /using conversion rules file.*t.rules/ 46 | >>>=0 47 | -------------------------------------------------------------------------------- /extra/hledger-vty/Hledger/Vty/Options.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TemplateHaskell #-} 2 | {-| 3 | 4 | -} 5 | 6 | module Hledger.Vty.Options 7 | where 8 | import Distribution.PackageDescription.TH (packageVariable, package, pkgName, pkgVersion) 9 | import System.Console.CmdArgs 10 | import System.Console.CmdArgs.Explicit 11 | 12 | import Hledger.Cli hiding (progname,progversion) 13 | 14 | progname = $(packageVariable (pkgName . package)) 15 | progversion = progname ++ " " ++ $(packageVariable (pkgVersion . package)) :: String 16 | 17 | vtyflags = [ 18 | flagNone ["debug-vty"] (\opts -> setboolopt "rules-file" opts) "run with no terminal output, showing console" 19 | ] 20 | 21 | vtymode = (mode "hledger-vty" [("command","vty")] 22 | "browse accounts, postings and entries in a full-window curses interface" 23 | commandargsflag []){ 24 | modeGroupFlags = Group { 25 | groupUnnamed = vtyflags 26 | ,groupHidden = [] 27 | ,groupNamed = [(generalflagstitle, generalflags1)] 28 | } 29 | ,modeHelpSuffix=[ 30 | -- "Reads your ~/.hledger.journal file, or another specified by $LEDGER_FILE or -f, and starts the full-window curses ui." 31 | ] 32 | } 33 | 34 | -- hledger-vty options, used in hledger-vty and above 35 | data VtyOpts = VtyOpts { 36 | debug_vty_ :: Bool 37 | ,cliopts_ :: CliOpts 38 | } deriving (Show) 39 | 40 | defvtyopts = VtyOpts 41 | def 42 | def 43 | 44 | -- instance Default CliOpts where def = defcliopts 45 | 46 | toVtyOpts :: RawOpts -> IO VtyOpts 47 | toVtyOpts rawopts = do 48 | cliopts <- toCliOpts rawopts 49 | return defvtyopts { 50 | debug_vty_ = boolopt "debug-vty" rawopts 51 | ,cliopts_ = cliopts 52 | } 53 | 54 | checkVtyOpts :: VtyOpts -> IO VtyOpts 55 | checkVtyOpts opts = do 56 | checkCliOpts $ cliopts_ opts 57 | return opts 58 | 59 | getHledgerVtyOpts :: IO VtyOpts 60 | getHledgerVtyOpts = processArgs vtymode >>= return . decodeRawOpts >>= toVtyOpts >>= checkVtyOpts 61 | 62 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: hledger Contributor List and Agreement 3 | --- 4 | 5 | # hledger Contributors 6 | 7 | hledger is brought to you by: 8 | 9 | - Simon Michael - lead developer, project manager, general dogsbody 10 | - Sergey Astanin - utf8 support 11 | - Nick Ingolia - parser improvements 12 | - Roman Cheplyaka - "chart" command, "add" command improvements 13 | - Michael Snoyman - some additions to the Yesod web interface 14 | - Marko Kocić - hlint cleanup 15 | 16 | Developers who have not yet signed the contributor agreement: 17 | 18 | - Tim Docker - parser improvements 19 | - Oliver Braun - GHC 6.12 compatibility 20 | - Gwern Branwen - cleanups 21 | 22 | Also: 23 | 24 | - John Wiegley - created the original ledger program 25 | 26 |
    27 | 28 | * * * * * 29 | 30 |
    31 | 32 | # hledger Contributor Agreement 33 | 34 | This is the official contributor agreement (v1) and contributor list for 35 | the hledger project. If you have contributed non-trivial code changes to 36 | hledger, please sign this, to help ensure: 37 | 38 | 1. that there is a clear legal status and audit trail for the project 39 | 40 | 2. that you get proper credit for your work 41 | 42 | 3. that we are able to remain license-compatible with related software 43 | by updating to newer versions of our license when appropriate (eg 44 | GPLv2 -\> GPLv3) 45 | 46 | To "sign", read the conditions below and then send a darcs patch to 47 | \`this file\`\_ adding your name to the Contributor List above. By so 48 | doing you promise that all of your commits to hledger: 49 | 50 | - are free of patent violations or copyright violations, to the best 51 | of your knowledge 52 | 53 | - are released under the hledger project's 54 | [license](http://joyful.com/repos/hledger/LICENSE); or are released 55 | under another compatible license (in which case this must be clearly 56 | noted); or are public domain 57 | 58 | - may be relicensed under official future versions of their license at 59 | the discretion of the project leader 60 | 61 | We don't currently gather paper signatures, but this is a good start. 62 | Feel free to update your entry as your contributions grow! 63 | -------------------------------------------------------------------------------- /tools/listbydeps.hs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env runhaskell 2 | 3 | import System 4 | import System.Directory 5 | import System.IO 6 | import Data.List 7 | import Data.Char 8 | import Data.Maybe 9 | import Data.Ord 10 | 11 | {- 12 | Read a Haskell *.hs file and get a list of all the modules 13 | that it imports. 14 | -} 15 | findDeps base pkg = do 16 | let hi = base ++ map dotToSlash pkg ++ ".hs" 17 | ex <- doesFileExist hi 18 | if not ex then return [] else do 19 | src <- readFile hi 20 | let imps = filter isImport (lines src) 21 | return $ mapMaybe getTargetModule imps 22 | 23 | where dotToSlash '.' = '/' 24 | dotToSlash x = x 25 | 26 | isImport (' ':t) = isImport t 27 | isImport ('\t':t) = isImport t 28 | isImport t = "import" `isPrefixOf` t 29 | 30 | getTargetModule s = let pre = takeWhile (/= '(') s in 31 | find (isUpper . head) (words pre) 32 | 33 | {- 34 | Find the transitive, reflexive closure of the relation defined 35 | by the findDeps function. This returns a list of ALL modules 36 | that this one uses or depends on, directly or indirectly. 37 | -} 38 | allDeps base mod = allDeps' [mod] [mod] where 39 | allDeps' (m:ms) full = do d <- findDeps base m 40 | let d' = filter (not . flip elem full) d 41 | t <- allDeps' (d' ++ ms) (d' ++ full) 42 | return (m : t) 43 | allDeps' [] _ = return [] 44 | 45 | {- 46 | Usage: OrderByComplexity 47 | 48 | = directory where source code is found. This MUST 49 | end in '/' 50 | = file that lists the modules you're interested in, 51 | one per line. This is often taken from a .cabal 52 | -} 53 | main = do [ base, pkgFile ] <- getArgs 54 | pkgStr <- readFile pkgFile 55 | let pkgs = lines pkgStr 56 | mods <- mapM (allDeps base) pkgs 57 | let deps = zip pkgs mods 58 | let deps' = sortBy (comparing fst) deps 59 | mapM_ (print . fst) (sortBy (comparing $ length . snd) deps') 60 | 61 | -------------------------------------------------------------------------------- /tests/balance-assertions.test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env shelltest 2 | # 1. test some balance assertions 3 | hledgerdev -f - stats 4 | <<< 5 | 2013/1/1 6 | a $1 =$1 7 | b =-$1 8 | 9 | 2013/1/2 10 | a $1 =$2 11 | b $-1 =$-2 12 | 13 | 2013/1/3 14 | a $1 = $3 15 | b $-1 = $-3 16 | (b) $1 = $-2 17 | 18 | >>> /Transactions/ 19 | >>>2 20 | >>>=0 21 | 22 | # 2. same entries as 1 but different parse order, assertion should still pass based on date 23 | hledgerdev -f - stats 24 | <<< 25 | 2013/1/1 26 | a $1 =$1 27 | b =-$1 28 | 29 | 2013/1/3 30 | a $1 = $3 31 | b $-1 = $-3 32 | (b) $1 = $-2 33 | 34 | 2013/1/2 35 | a $1 =$2 36 | b $-1 =$-2 37 | 38 | >>> /Transactions/ 39 | >>>2 40 | >>>=0 41 | 42 | # 3. like 1 but switch order of postings in last entry, 43 | # assertion should fail and exit code should be non zero 44 | hledgerdev -f - stats 45 | <<< 46 | 2013/1/1 47 | a $1 =$1 48 | b =-$1 49 | 50 | 2013/1/2 51 | a $1 =$2 52 | b $-1 =$-2 53 | 54 | 2013/1/3 55 | a $1 = $3 56 | (b) $1 = $-2 57 | b $-1 = $-3 58 | 59 | >>> 60 | >>>2 /Balance assertion failed for account b on 2013-01-03/ 61 | >>>=1 62 | 63 | # 4. should also work without commodity symbols 64 | hledgerdev -f - stats 65 | <<< 66 | 2013/1/1 67 | (a) 1 =1 68 | 69 | >>> /Transactions/ 70 | >>>2 71 | >>>=0 72 | 73 | # 5. should work for fractional amount with trailing zeros 74 | hledgerdev -f - stats 75 | <<< 76 | 2013/1/1 77 | a $1.20 =$1.20 78 | b =-$1.20 79 | 80 | 2013/1/2 81 | a $0.10 =$1.3 82 | b =-$1.3 83 | 84 | 2013/1/3 85 | a $0.7 =$2 86 | b =-$2 87 | 88 | >>> /Transactions/ 89 | >>>2 90 | >>>=0 91 | 92 | # 6. what should happen here ? Currently, 93 | # in a, 3.4 EUR @@ $5.6 and -3.4 EUR cancel out (wrong ?) 94 | # in b, 95 | # 96 | # hledgerdev -f - stats 97 | # <<< 98 | # 2013/1/1 99 | # a $1.20 100 | # a 3.4 EUR @@ $5.6 101 | # b 102 | 103 | # 2013/1/2 104 | # a -3.4 EUR 105 | # b 106 | 107 | # 2013/1/3 108 | # a $0.1 =$1.30 109 | # b =-$1.30 110 | 111 | # >>> /Transactions/ 112 | # >>>2 113 | # >>>=0 114 | -------------------------------------------------------------------------------- /extra/hledger-vty/hledger-vty.cabal: -------------------------------------------------------------------------------- 1 | name: hledger-vty 2 | version: 0.16.1 3 | category: Finance 4 | synopsis: A curses-style console interface for the hledger accounting tool. 5 | description: 6 | hledger is a library and set of user tools for working 7 | with financial data (or anything that can be tracked in a 8 | double-entry accounting ledger.) It is a haskell port and 9 | friendly fork of John Wiegley's Ledger. hledger provides 10 | command-line, curses and web interfaces, and aims to be a 11 | reliable, practical tool for daily use. 12 | 13 | license: GPL 14 | license-file: LICENSE 15 | author: Simon Michael 16 | maintainer: Simon Michael 17 | homepage: http://hledger.org 18 | bug-reports: http://code.google.com/p/hledger/issues 19 | stability: beta 20 | tested-with: GHC==6.10, GHC==6.12 21 | cabal-version: >= 1.6 22 | build-type: Simple 23 | -- data-dir: data 24 | -- data-files: 25 | extra-tmp-files: 26 | extra-source-files: 27 | 28 | source-repository head 29 | type: darcs 30 | location: http://joyful.com/repos/hledger 31 | 32 | executable hledger-vty 33 | main-is: hledger-vty.hs 34 | ghc-options: -threaded -W 35 | other-modules: 36 | Hledger.Vty 37 | Hledger.Vty.Main 38 | Hledger.Vty.Options 39 | build-depends: 40 | hledger == 0.16.1 41 | ,hledger-lib == 0.16.1 42 | ,HUnit 43 | ,base >= 3 && < 5 44 | ,cabal-file-th 45 | ,cmdargs >= 0.9.1 && < 0.10 46 | -- ,containers 47 | -- ,csv 48 | -- ,directory 49 | -- ,filepath 50 | -- ,mtl 51 | -- ,old-locale 52 | -- ,old-time 53 | -- ,parsec 54 | -- ,process 55 | -- ,regexpr >= 0.5.1 56 | ,safe >= 0.2 57 | -- ,split == 0.1.* 58 | ,time 59 | -- ,utf8-string >= 0.3.5 && < 0.4 60 | ,vty >= 4.6.0.1 && < 4.8 61 | -------------------------------------------------------------------------------- /extra/hledger-chart/hledger-chart.cabal: -------------------------------------------------------------------------------- 1 | name: hledger-chart 2 | version: 0.16.1 3 | category: Finance 4 | synopsis: A pie chart image generator for the hledger accounting tool. 5 | description: 6 | hledger is a library and set of user tools for working 7 | with financial data (or anything that can be tracked in a 8 | double-entry accounting ledger.) It is a haskell port and 9 | friendly fork of John Wiegley's Ledger. hledger provides 10 | command-line, curses and web interfaces, and aims to be a 11 | reliable, practical tool for daily use. 12 | 13 | license: GPL 14 | license-file: LICENSE 15 | author: Simon Michael 16 | maintainer: Simon Michael 17 | homepage: http://hledger.org 18 | bug-reports: http://code.google.com/p/hledger/issues 19 | stability: experimental 20 | tested-with: GHC==6.10, GHC==6.12 21 | cabal-version: >= 1.6 22 | build-type: Simple 23 | -- data-dir: data 24 | -- data-files: 25 | extra-tmp-files: 26 | extra-source-files: 27 | 28 | source-repository head 29 | type: darcs 30 | location: http://joyful.com/repos/hledger 31 | 32 | executable hledger-chart 33 | main-is: hledger-chart.hs 34 | ghc-options: -threaded -W 35 | other-modules: 36 | Hledger.Chart 37 | Hledger.Chart.Main 38 | Hledger.Chart.Options 39 | build-depends: 40 | hledger == 0.16.1 41 | ,hledger-lib == 0.16.1 42 | ,HUnit 43 | ,base >= 3 && < 5 44 | ,cabal-file-th 45 | ,cmdargs >= 0.9.1 && < 0.10 46 | ,containers 47 | -- ,csv 48 | -- ,directory 49 | -- ,filepath 50 | -- ,mtl 51 | -- ,old-locale 52 | -- ,old-time 53 | -- ,parsec 54 | -- ,process 55 | -- ,regexpr >= 0.5.1 56 | ,safe >= 0.2 57 | -- ,split == 0.1.* 58 | ,time 59 | -- ,utf8-string >= 0.3.5 && < 0.4 60 | ,Chart >= 0.11 && < 0.15 61 | ,colour 62 | -------------------------------------------------------------------------------- /tests/tags.test: -------------------------------------------------------------------------------- 1 | # 1. we parse metadata tags in transaction and posting comments. Currently, 2 | # - they can be on the same line and/or separate lines 3 | # - they are always printed on separate lines 4 | hledgerdev -f - print 5 | <<< 6 | 2010/01/01 ; txntag1: txn val 1 7 | ; txntag2: txn val 2 8 | a 1 9 | ; posting1tag1: posting 1 val 1 10 | ; posting1tag2: 11 | b -1 ; posting-2-tag-1: posting 2 val 1 12 | ; posting-2-tag-2: 13 | ; non-metadata: 14 | >>> 15 | 2010/01/01 16 | ; txntag1: txn val 1 17 | ; txntag2: txn val 2 18 | a 1 19 | ; posting1tag1: posting 1 val 1 20 | ; posting1tag2: 21 | b -1 22 | ; posting-2-tag-1: posting 2 val 1 23 | ; posting-2-tag-2: 24 | 25 | >>>2 26 | >>>=0 27 | 28 | # 2. reports can filter by tag existence 29 | hledgerdev -f - print tag:foo 30 | <<< 31 | 2010/01/01 ; foo:bar 32 | a 1 33 | b -1 34 | 35 | 2010/01/02 ; foo:baz 36 | c 1 37 | d -1 38 | 39 | 2010/01/03 40 | e 1 41 | f -1 42 | >>> 43 | 2010/01/01 ; foo:bar 44 | a 1 45 | b -1 46 | 47 | 2010/01/02 ; foo:baz 48 | c 1 49 | d -1 50 | 51 | >>>2 52 | >>>=0 53 | 54 | # 3. or tag value 55 | hledgerdev -f - print tag:foo=bar 56 | <<< 57 | 2010/01/01 ; foo:bar 58 | a 1 59 | b -1 60 | 61 | 2010/01/02 62 | ; foo:baz 63 | c 1 64 | d -1 65 | 66 | 2010/01/03 67 | e 1 68 | f -1 69 | >>> 70 | 2010/01/01 ; foo:bar 71 | a 1 72 | b -1 73 | 74 | >>>2 75 | >>>=0 76 | 77 | # 4. postings inherit their transaction's tags 78 | hledgerdev -f - register tag:foo=bar 79 | <<< 80 | 2010/01/01 81 | a 1 ; foo:bar 82 | b -1 83 | 84 | 2010/01/02 ; foo:baz 85 | c 1 86 | d -1 87 | 88 | 2010/01/03 ; foo:bar 89 | e 1 90 | f -1 91 | >>> 92 | 2010/01/01 a 1 1 93 | 2010/01/03 e 1 2 94 | f -1 1 95 | >>>2 96 | >>>=0 97 | 98 | -------------------------------------------------------------------------------- /hledger/Hledger/Cli/Print.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | 3 | A ledger-compatible @print@ command. 4 | 5 | -} 6 | 7 | module Hledger.Cli.Print ( 8 | print' 9 | ,showTransactions 10 | ,tests_Hledger_Cli_Print 11 | ) where 12 | import Data.List 13 | import Test.HUnit 14 | 15 | import Hledger 16 | import Prelude hiding (putStr) 17 | import Hledger.Utils.UTF8IOCompat (putStr) 18 | import Hledger.Cli.Options 19 | 20 | -- | Print journal transactions in standard format. 21 | print' :: CliOpts -> Journal -> IO () 22 | print' CliOpts{reportopts_=ropts} j = do 23 | d <- getCurrentDay 24 | putStr $ showTransactions ropts (queryFromOpts d ropts) j 25 | 26 | showTransactions :: ReportOpts -> Query -> Journal -> String 27 | showTransactions opts q j = entriesReportAsText opts q $ entriesReport opts q j 28 | 29 | tests_showTransactions = [ 30 | "showTransactions" ~: do 31 | 32 | -- "print expenses" ~: 33 | do 34 | let opts = defreportopts{query_="expenses"} 35 | d <- getCurrentDay 36 | showTransactions opts (queryFromOpts d opts) samplejournal `is` unlines 37 | ["2008/06/03 * eat & shop" 38 | ," expenses:food $1" 39 | ," expenses:supplies $1" 40 | ," assets:cash $-2" 41 | ,"" 42 | ] 43 | 44 | -- , "print report with depth arg" ~: 45 | do 46 | let opts = defreportopts{depth_=Just 2} 47 | d <- getCurrentDay 48 | showTransactions opts (queryFromOpts d opts) samplejournal `is` unlines 49 | ["2008/01/01 income" 50 | ," assets:bank:checking $1" 51 | ," income:salary $-1" 52 | ,"" 53 | ,"2008/06/01 gift" 54 | ," assets:bank:checking $1" 55 | ," income:gifts $-1" 56 | ,"" 57 | ,"2008/06/03 * eat & shop" 58 | ," expenses:food $1" 59 | ," expenses:supplies $1" 60 | ," assets:cash $-2" 61 | ,"" 62 | ,"2008/12/31 * pay off" 63 | ," liabilities:debts $1" 64 | ," assets:bank:checking $-1" 65 | ,"" 66 | ] 67 | ] 68 | 69 | entriesReportAsText :: ReportOpts -> Query -> EntriesReport -> String 70 | entriesReportAsText _ _ items = concatMap showTransactionUnelided items 71 | 72 | tests_Hledger_Cli_Print = TestList 73 | tests_showTransactions -------------------------------------------------------------------------------- /hledger-web/templates/default-layout-wrapper.hamlet: -------------------------------------------------------------------------------- 1 | $newline never 2 | \ 3 | \ 4 | \ 5 | \ 6 | \ 7 | 8 | 9 | 10 | 11 | #{pageTitle pc} 12 | <meta name="description" content=""> 13 | <meta name="author" content=""> 14 | 15 | <meta name="viewport" content="width=device-width,initial-scale=1"> 16 | 17 | ^{pageHead pc} 18 | 19 | \<!--[if lt IE 9]> 20 | \<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script> 21 | \<![endif]--> 22 | 23 | <script> 24 | document.documentElement.className = document.documentElement.className.replace(/\bno-js\b/,'js'); 25 | <body> 26 | <div class="container"> 27 | <header> 28 | <div id="outermain" role="main"> 29 | ^{pageBody pc} 30 | <footer> 31 | #{extraCopyright $ appExtra $ settings master} 32 | 33 | $maybe analytics <- extraAnalytics $ appExtra $ settings master 34 | <script> 35 | if(!window.location.href.match(/localhost/)){ 36 | window._gaq = [['_setAccount','#{analytics}'],['_trackPageview'],['_trackPageLoadTime']]; 37 | (function() { 38 | \ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; 39 | \ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; 40 | \ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); 41 | })(); 42 | } 43 | \<!-- Prompt IE 6 users to install Chrome Frame. Remove this if you want to support IE 6. chromium.org/developers/how-tos/chrome-frame-getting-started --> 44 | \<!--[if lt IE 7 ]> 45 | <script src="//ajax.googleapis.com/ajax/libs/chrome-frame/1.0.3/CFInstall.min.js"> 46 | <script> 47 | window.attachEvent('onload',function(){CFInstall.check({mode:'overlay'})}) 48 | \<![endif]--> 49 | -------------------------------------------------------------------------------- /site/templates/default.html: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 | "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 4 | <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> 5 | <head> 6 | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 7 | <title>$title$ 8 | 9 | 10 | 11 | 16 | 17 | 18 | Fork me on GitHub 19 |
    20 | 21 |
    22 | 30 | $body$ 31 | 35 | 36 | 40 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /hledger-lib/Hledger/Data/Commodity.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | 3 | A 'Commodity' is a symbol representing a currency or some other kind of 4 | thing we are tracking, and some display preferences that tell how to 5 | display 'Amount's of the commodity - is the symbol on the left or right, 6 | are thousands separated by comma, significant decimal places and so on. 7 | 8 | -} 9 | module Hledger.Data.Commodity 10 | where 11 | import Data.List 12 | import Data.Maybe (fromMaybe) 13 | import Test.HUnit 14 | -- import qualified Data.Map as M 15 | 16 | import Hledger.Data.Types 17 | import Hledger.Utils 18 | 19 | 20 | -- characters than can't be in a non-quoted commodity symbol 21 | nonsimplecommoditychars = "0123456789-.@;\n \"{}=" :: String 22 | 23 | quoteCommoditySymbolIfNeeded s | any (`elem` nonsimplecommoditychars) s = "\"" ++ s ++ "\"" 24 | | otherwise = s 25 | 26 | commodity = "" 27 | 28 | -- handy constructors for tests 29 | -- unknown = commodity 30 | -- usd = "$" 31 | -- eur = "€" 32 | -- gbp = "£" 33 | -- hour = "h" 34 | 35 | -- Some sample commodity' names and symbols, for use in tests.. 36 | commoditysymbols = 37 | [("unknown","") 38 | ,("usd","$") 39 | ,("eur","€") 40 | ,("gbp","£") 41 | ,("hour","h") 42 | ] 43 | 44 | -- | Look up one of the sample commodities' symbol by name. 45 | comm :: String -> Commodity 46 | comm name = snd $ fromMaybe 47 | (error' "commodity lookup failed") 48 | (find (\n -> fst n == name) commoditysymbols) 49 | 50 | -- | Find the conversion rate between two commodities. Currently returns 1. 51 | conversionRate :: Commodity -> Commodity -> Double 52 | conversionRate _ _ = 1 53 | 54 | -- -- | Convert a list of commodities to a map from commodity symbols to 55 | -- -- unique, display-preference-canonicalised commodities. 56 | -- canonicaliseCommodities :: [Commodity] -> Map.Map String Commodity 57 | -- canonicaliseCommodities cs = 58 | -- Map.fromList [(s,firstc{precision=maxp}) | s <- symbols, 59 | -- let cs = commoditymap ! s, 60 | -- let firstc = head cs, 61 | -- let maxp = maximum $ map precision cs 62 | -- ] 63 | -- where 64 | -- commoditymap = Map.fromList [(s, commoditieswithsymbol s) | s <- symbols] 65 | -- commoditieswithsymbol s = filter ((s==) . symbol) cs 66 | -- symbols = nub $ map symbol cs 67 | 68 | tests_Hledger_Data_Commodity = TestList [ 69 | ] 70 | 71 | -------------------------------------------------------------------------------- /site/js/highslide/highslide-ie6.css: -------------------------------------------------------------------------------- 1 | .closebutton { 2 | /* NOTE! This URL is relative to the HTML page, not the CSS */ 3 | filter:progid:DXImageTransform.Microsoft.AlphaImageLoader( 4 | src='../highslide/graphics/close.png', sizingMethod='scale'); 5 | 6 | background: none; 7 | cursor: hand; 8 | } 9 | 10 | /* Viewport fixed hack */ 11 | .highslide-viewport { 12 | position: absolute; 13 | left: expression( ( ( ignoreMe1 = document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft ) ) + 'px' ); 14 | top: expression( ( ignoreMe2 = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop ) + 'px' ); 15 | width: expression( ( ( ignoreMe3 = document.documentElement.clientWidth ? document.documentElement.clientWidth : document.body.clientWidth ) ) + 'px' ); 16 | height: expression( ( ( ignoreMe4 = document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight ) ) + 'px' ); 17 | } 18 | 19 | /* Thumbstrip PNG fix */ 20 | .highslide-scroll-down, .highslide-scroll-up { 21 | position: relative; 22 | overflow: hidden; 23 | } 24 | .highslide-scroll-down div, .highslide-scroll-up div { 25 | /* NOTE! This URL is relative to the HTML page, not the CSS */ 26 | filter:progid:DXImageTransform.Microsoft.AlphaImageLoader( 27 | src='../highslide/graphics/scrollarrows.png', sizingMethod='scale'); 28 | background: none !important; 29 | position: absolute; 30 | cursor: hand; 31 | width: 75px; 32 | height: 75px !important; 33 | } 34 | .highslide-thumbstrip-horizontal .highslide-scroll-down div { 35 | left: -50px; 36 | top: -15px; 37 | } 38 | .highslide-thumbstrip-horizontal .highslide-scroll-up div { 39 | top: -15px; 40 | } 41 | .highslide-thumbstrip-vertical .highslide-scroll-down div { 42 | top: -50px; 43 | } 44 | 45 | /* Thumbstrip marker arrow trasparent background fix */ 46 | .highslide-thumbstrip .highslide-marker { 47 | border-color: white; /* match the background */ 48 | } 49 | .dark .highslide-thumbstrip-horizontal .highslide-marker { 50 | border-color: #111; 51 | } 52 | .highslide-viewport .highslide-marker { 53 | border-color: #333; 54 | } 55 | .highslide-thumbstrip { 56 | float: left; 57 | } 58 | 59 | /* Positioning fixes for the control bar */ 60 | .text-controls .highslide-controls { 61 | width: 480px; 62 | } 63 | .text-controls a span { 64 | width: 4em; 65 | } 66 | .text-controls .highslide-full-expand a span { 67 | width: 0; 68 | } 69 | .text-controls .highslide-close a span { 70 | width: 0; 71 | } 72 | -------------------------------------------------------------------------------- /hledger-web/Application.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -fno-warn-orphans #-} 2 | module Application 3 | ( makeApplication 4 | , getApplicationDev 5 | , makeFoundation 6 | ) where 7 | 8 | import Data.IORef 9 | import Import 10 | import Yesod.Default.Config 11 | import Yesod.Default.Main 12 | import Yesod.Default.Handlers 13 | import Network.Wai.Middleware.RequestLogger (logStdoutDev, logStdout) 14 | import Network.HTTP.Conduit (newManager, def) 15 | 16 | -- Import all relevant handler modules here. 17 | -- Don't forget to add new modules to your cabal file! 18 | import Handler.RootR 19 | import Handler.JournalR 20 | import Handler.JournalEditR 21 | import Handler.JournalEntriesR 22 | import Handler.RegisterR 23 | 24 | import Hledger.Web.Options (WebOpts(..), defwebopts) 25 | import Hledger.Data (Journal, nulljournal) 26 | import Hledger.Read (readJournalFile) 27 | import Hledger.Utils (error') 28 | import Hledger.Cli.Options (defcliopts, journalFilePathFromOpts) 29 | 30 | -- This line actually creates our YesodDispatch instance. It is the second half 31 | -- of the call to mkYesodData which occurs in Foundation.hs. Please see the 32 | -- comments there for more details. 33 | mkYesodDispatch "App" resourcesApp 34 | 35 | -- This function allocates resources (such as a database connection pool), 36 | -- performs initialization and creates a WAI application. This is also the 37 | -- place to put your migrate statements to have automatic database 38 | -- migrations handled by Yesod. 39 | makeApplication :: WebOpts -> Journal -> AppConfig DefaultEnv Extra -> IO Application 40 | makeApplication opts j conf = do 41 | foundation <- makeFoundation conf 42 | writeIORef (appJournal foundation) j 43 | app <- toWaiAppPlain foundation 44 | return $ logWare app 45 | where 46 | logWare | development = logStdoutDev 47 | | server_ opts = logStdout 48 | | otherwise = id 49 | 50 | makeFoundation :: AppConfig DefaultEnv Extra -> IO App 51 | makeFoundation conf = do 52 | manager <- newManager def 53 | s <- staticSite 54 | jref <- newIORef nulljournal 55 | return $ App conf s manager defwebopts jref 56 | 57 | -- for yesod devel 58 | -- uses the journal specified by the LEDGER_FILE env var, or ~/.hledger.journal 59 | getApplicationDev :: IO (Int, Application) 60 | getApplicationDev = do 61 | f <- journalFilePathFromOpts defcliopts 62 | j <- either error' id `fmap` readJournalFile Nothing Nothing f 63 | defaultDevelApp loader (makeApplication defwebopts j) 64 | where 65 | loader = Yesod.Default.Config.loadConfig (configSettings Development) 66 | { csParseExtra = parseExtra 67 | } 68 | -------------------------------------------------------------------------------- /hledger/Hledger/Cli/Version.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP, TemplateHaskell #-} 2 | {- 3 | Version number-related utilities. See also the Makefile. 4 | -} 5 | 6 | module Hledger.Cli.Version ( 7 | progname, 8 | version, 9 | prognameandversion, 10 | binaryfilename 11 | ) 12 | where 13 | import System.Info (os, arch) 14 | import Text.Printf 15 | 16 | import Hledger.Utils 17 | 18 | 19 | -- package name and version from the cabal file 20 | progname, version, prognameandversion :: String 21 | progname = "hledger" 22 | #ifdef VERSION 23 | version = VERSION 24 | #else 25 | version = "" 26 | #endif 27 | prognameandversion = progname ++ " " ++ version 28 | 29 | -- developer build version strings include PATCHLEVEL (number of 30 | -- patches since the last tag). If defined, it must be a number. 31 | patchlevel :: String 32 | #ifdef PATCHLEVEL 33 | patchlevel = "." ++ show (PATCHLEVEL :: Int) 34 | #else 35 | patchlevel = "" 36 | #endif 37 | 38 | -- the package version plus patchlevel if specified 39 | buildversion :: String 40 | buildversion = version ++ patchlevel 41 | 42 | -- | Given a program name, return a precise platform-specific executable 43 | -- name suitable for naming downloadable binaries. Can raise an error if 44 | -- the version and patch level was not defined correctly at build time. 45 | binaryfilename :: String -> String 46 | binaryfilename progname = prettify $ splitAtElement '.' buildversion 47 | where 48 | prettify (major:minor:bugfix:patches:[]) = 49 | printf "%s-%s.%s%s%s-%s-%s%s" progname major minor bugfix' patches' os' arch suffix 50 | where 51 | bugfix' 52 | | bugfix `elem` ["0"{-,"98","99"-}] = "" 53 | | otherwise = '.' : bugfix 54 | patches' 55 | | patches/="0" = '+' : patches 56 | | otherwise = "" 57 | (os',suffix) 58 | | os == "darwin" = ("mac","" :: String) 59 | | os == "mingw32" = ("windows",".exe") 60 | | otherwise = (os,"") 61 | prettify (major:minor:bugfix:[]) = prettify [major,minor,bugfix,"0"] 62 | prettify (major:minor:[]) = prettify [major,minor,"0","0"] 63 | prettify (major:[]) = prettify [major,"0","0","0"] 64 | prettify [] = error' "VERSION is empty, please fix" 65 | prettify _ = error' "VERSION has too many components, please fix" 66 | -------------------------------------------------------------------------------- /tests/add.test: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # data validation 3 | # 4 | # 1. should prompt again for a bad date 5 | hledgerdev -f $$-add.j add; rm -f $$-add.j 6 | <<< 7 | 2009/1/32 8 | >>> /date .*: date .*/ 9 | >>>=0 10 | 11 | # 2. should accept a blank date 12 | hledgerdev -f $$-add.j add; rm -f $$-add.j 13 | <<< 14 | 15 | >>> /date .*: description / 16 | >>>=0 17 | 18 | ############################################################################## 19 | # precision and commodity handling 20 | # 21 | # 3. simple add with no existing journal, no commodity entered 22 | hledgerdev -f $$-add.j add; rm -f $$-add.j 23 | <<< 24 | 25 | 26 | a 27 | 1000.0 28 | b 29 | 30 | . 31 | >>> /^date.*: description.*: account 1.*: amount 1.*: account 2.*: amount 2.*: account 3.*or \. to complete.*: Accept.*: $/ 32 | >>>=0 33 | 34 | # 4. default commodity with greater precision 35 | printf 'D $1000.00\n' >t$$.j; hledgerdev -f t$$.j add >/dev/null; cat t$$.j; rm -f t$$.j 36 | <<< 37 | 38 | 39 | a 40 | $1000.0 41 | b 42 | 43 | . 44 | 45 | >>> /a +\$1000\.0/ 46 | >>>=0 47 | 48 | # 5. default commodity with less precision 49 | printf 'D $1000.0\n' >t$$.j; hledgerdev -f t$$.j add >/dev/null; cat t$$.j; rm -f t$$.j 50 | <<< 51 | 52 | 53 | a 54 | $1000.00 55 | b 56 | 57 | . 58 | 59 | >>> /a +\$1000\.00/ 60 | >>>=0 61 | 62 | # 6. existing commodity with greater precision 63 | printf '2010/1/1\n a $1000.00\n b\n' >t$$.j; hledgerdev -f t$$.j add >/dev/null; cat t$$.j; rm -f t$$.j 64 | <<< 65 | 66 | 67 | a 68 | $1000.0 69 | b 70 | 71 | . 72 | >>> /a +\$1000\.0/ 73 | >>>=0 74 | 75 | # 7. existing commodity with less precision 76 | printf '2010/1/1\n a $1000.0\n b\n' >t$$.j; hledgerdev -f t$$.j add >/dev/null; cat t$$.j; rm -f t$$.j 77 | <<< 78 | 79 | 80 | a 81 | $1000.00 82 | b 83 | 84 | . 85 | 86 | >>> /a +\$1000\.00/ 87 | >>>=0 88 | 89 | # 8. no commodity entered, the (most recent) default commodity should be applied 90 | # (and a non-ascii commodity symbol should work) 91 | printf 'D $1000.0\nD £1,000.00\n' >t$$.j; hledgerdev -f t$$.j add; cat t$$.j; rm -f t$$.j 92 | <<< 93 | 2010/1/1 94 | 95 | a 96 | 1000 97 | b 98 | 99 | . 100 | 101 | >>> /a +£1,000.00/ 102 | >>>=0 103 | 104 | # 9. default amounts should not fail to balance due to precision 105 | rm -f nosuch.journal; hledgerdev -f nosuch.journal add; rm -f nosuch.journal 106 | <<< 107 | 2010/1/1 108 | x 109 | a 110 | 0.25 111 | b 112 | 0.5 113 | c 114 | >>> /amount 3 \? \[-0.75\]/ 115 | >>>=0 116 | 117 | ## 10. shouldn't add decimals if there aren't any 118 | ## printf '\n\na\n1\nb\n' | hledgerdev -f /dev/null add 119 | # hledgerdev -f /dev/null add 120 | # <<< 121 | 122 | 123 | # a 124 | # 1 125 | # b 126 | # >>> /amount 2 \[-1\]/ 127 | # >>>=0 128 | 129 | -------------------------------------------------------------------------------- /extra/hledger-chart/Hledger/Chart/Options.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TemplateHaskell #-} 2 | {-| 3 | 4 | -} 5 | 6 | module Hledger.Chart.Options 7 | where 8 | import Data.Maybe 9 | import Distribution.PackageDescription.TH (packageVariable, package, pkgName, pkgVersion) 10 | import System.Console.CmdArgs 11 | import System.Console.CmdArgs.Explicit 12 | 13 | import Hledger.Cli hiding (progname,progversion) 14 | import qualified Hledger.Cli (progname) 15 | 16 | progname = $(packageVariable (pkgName . package)) 17 | progversion = progname ++ " " ++ $(packageVariable (pkgVersion . package)) :: String 18 | 19 | defchartoutput = "hledger.png" 20 | defchartitems = 10 21 | defchartsize = "600x400" 22 | 23 | chartflags = [ 24 | flagReq ["chart-output","o"] (\s opts -> Right $ setopt "chart-output" s opts) "IMGFILE" ("output filename (default: "++defchartoutput++")") 25 | ,flagReq ["chart-items"] (\s opts -> Right $ setopt "chart-items" s opts) "N" ("number of accounts to show (default: "++show defchartitems++")") 26 | ,flagReq ["chart-size"] (\s opts -> Right $ setopt "chart-size" s opts) "WIDTHxHEIGHT" ("image size (default: "++defchartsize++")") 27 | ] 28 | 29 | chartmode = (mode "hledger-chart" [("command","chart")] 30 | "generate a pie chart image for the top account balances (of one sign only)" 31 | commandargsflag []){ 32 | modeGroupFlags = Group { 33 | groupUnnamed = chartflags 34 | ,groupHidden = [] 35 | ,groupNamed = [(generalflagstitle, generalflags1)] 36 | } 37 | ,modeHelpSuffix=[ 38 | -- "Reads your ~/.hledger.journal file, or another specified by $LEDGER_FILE or -f, and starts the full-window curses ui." 39 | ] 40 | } 41 | 42 | -- hledger-chart options, used in hledger-chart and above 43 | data ChartOpts = ChartOpts { 44 | chart_output_ :: FilePath 45 | ,chart_items_ :: Int 46 | ,chart_size_ :: String 47 | ,cliopts_ :: CliOpts 48 | } deriving (Show) 49 | 50 | defchartopts = ChartOpts 51 | def 52 | def 53 | def 54 | def 55 | 56 | -- instance Default CliOpts where def = defcliopts 57 | 58 | toChartOpts :: RawOpts -> IO ChartOpts 59 | toChartOpts rawopts = do 60 | cliopts <- toCliOpts rawopts 61 | return defchartopts { 62 | chart_output_ = fromMaybe defchartoutput $ maybestringopt "debug-chart" rawopts 63 | ,chart_items_ = fromMaybe defchartitems $ maybeintopt "debug-items" rawopts 64 | ,chart_size_ = fromMaybe defchartsize $ maybestringopt "debug-size" rawopts 65 | ,cliopts_ = cliopts 66 | } 67 | 68 | checkChartOpts :: ChartOpts -> IO ChartOpts 69 | checkChartOpts opts = do 70 | checkCliOpts $ cliopts_ opts 71 | return opts 72 | 73 | getHledgerChartOpts :: IO ChartOpts 74 | getHledgerChartOpts = processArgs chartmode >>= return . decodeRawOpts >>= toChartOpts >>= checkChartOpts 75 | 76 | -------------------------------------------------------------------------------- /.boring: -------------------------------------------------------------------------------- 1 | # Boring file regexps: 2 | 3 | ### compiler and interpreter intermediate files 4 | # haskell (ghc) interfaces 5 | \.hi$ 6 | \.hi-boot$ 7 | \.o-boot$ 8 | # object files 9 | \.o$ 10 | \.o\.cmd$ 11 | # profiling haskell 12 | \.p_hi$ 13 | \.p_o$ 14 | # haskell program coverage resp. profiling info 15 | \.tix$ 16 | \.prof$ 17 | # fortran module files 18 | \.mod$ 19 | # linux kernel 20 | \.ko\.cmd$ 21 | \.mod\.c$ 22 | (^|/)\.tmp_versions($|/) 23 | # *.ko files aren't boring by default because they might 24 | # be Korean translations rather than kernel modules 25 | # \.ko$ 26 | # python, emacs, java byte code 27 | \.py[co]$ 28 | \.elc$ 29 | \.class$ 30 | # objects and libraries; lo and la are libtool things 31 | \.(obj|a|exe|so|lo|la)$ 32 | # compiled zsh configuration files 33 | \.zwc$ 34 | # Common LISP output files for CLISP and CMUCL 35 | \.(fas|fasl|sparcf|x86f)$ 36 | 37 | ### build and packaging systems 38 | # cabal intermediates 39 | \.installed-pkg-config 40 | \.setup-config 41 | # standard cabal build dir, might not be boring for everybody 42 | # ^dist(/|$) 43 | # autotools 44 | (^|/)autom4te\.cache($|/) 45 | (^|/)config\.(log|status)$ 46 | # microsoft web expression, visual studio metadata directories 47 | \_vti_cnf$ 48 | \_vti_pvt$ 49 | # gentoo tools 50 | \.revdep-rebuild.* 51 | # generated dependencies 52 | ^\.depend$ 53 | 54 | ### version control systems 55 | # cvs 56 | (^|/)CVS($|/) 57 | \.cvsignore$ 58 | # cvs, emacs locks 59 | ^\.# 60 | # rcs 61 | (^|/)RCS($|/) 62 | ,v$ 63 | # subversion 64 | (^|/)\.svn($|/) 65 | # mercurial 66 | (^|/)\.hg($|/) 67 | # git 68 | (^|/)\.git($|/) 69 | (^|/)\.gitignore($|/) 70 | # bzr 71 | \.bzr$ 72 | # sccs 73 | (^|/)SCCS($|/) 74 | # darcs 75 | (^|/)_darcs($|/) 76 | (^|/)\.darcsrepo($|/) 77 | ^\.darcs-temp-mail$ 78 | -darcs-backup[[:digit:]]+$ 79 | # gnu arch 80 | (^|/)(\+|,) 81 | (^|/)vssver\.scc$ 82 | \.swp$ 83 | (^|/)MT($|/) 84 | (^|/)\{arch\}($|/) 85 | (^|/).arch-ids($|/) 86 | # bitkeeper 87 | (^|/)BitKeeper($|/) 88 | (^|/)ChangeSet($|/) 89 | 90 | ### miscellaneous 91 | # backup files 92 | ~$ 93 | \.bak$ 94 | \.BAK$ 95 | # patch originals and rejects 96 | \.orig$ 97 | \.rej$ 98 | # X server 99 | \..serverauth.* 100 | # image spam 101 | \# 102 | (^|/)Thumbs\.db$ 103 | # vi, emacs tags 104 | (^|/)(tags|TAGS)$ 105 | #(^|/)\.[^/] 106 | # core dumps 107 | (^|/|\.)core$ 108 | # partial broken files (KIO copy operations) 109 | \.part$ 110 | # waf files, see http://code.google.com/p/waf/ 111 | (^|/)\.waf-[[:digit:].]+-[[:digit:]]+($|/) 112 | (^|/)\.lock-wscript$ 113 | # mac os finder 114 | (^|/)\.DS_Store$ 115 | 116 | 117 | # cabal stuff 118 | (^|/)dist$ 119 | 120 | # profiles etc. 121 | (^|/)profs($|/) 122 | 123 | # compiled tools 124 | tools/doctest$ 125 | tools/generateledger 126 | tools/simplifyprof 127 | 128 | # site stuff not explicitly added 129 | site/.* 130 | 131 | # compiled binaries 132 | bin/* 133 | 134 | # branch repos 135 | (^|/)b[[:digit:]]+ 136 | 137 | # yesod stuff 138 | (^|/)static/tmp 139 | client_session_key.aes 140 | -------------------------------------------------------------------------------- /hledger-web/Hledger/Web/Options.hs: -------------------------------------------------------------------------------- 1 | module Hledger.Web.Options 2 | where 3 | import Prelude 4 | import Data.Maybe 5 | import System.Console.CmdArgs 6 | import System.Console.CmdArgs.Explicit 7 | 8 | import Hledger.Cli hiding (progname,version,prognameandversion) 9 | import Settings 10 | 11 | progname, version :: String 12 | progname = "hledger-web" 13 | #ifdef VERSION 14 | version = VERSION 15 | #else 16 | version = "" 17 | #endif 18 | prognameandversion :: String 19 | prognameandversion = progname ++ " " ++ version :: String 20 | 21 | defbaseurlexample :: String 22 | defbaseurlexample = (reverse $ drop 4 $ reverse $ defbaseurl defport) ++ "PORT" 23 | 24 | webflags :: [Flag [([Char], [Char])]] 25 | webflags = [ 26 | flagNone ["server"] (setboolopt "server") ("log requests, don't auto-exit") 27 | ,flagReq ["base-url"] (\s opts -> Right $ setopt "base-url" s opts) "URL" ("set the base url (default: "++defbaseurlexample++")") 28 | ,flagReq ["port"] (\s opts -> Right $ setopt "port" s opts) "PORT" ("listen on this tcp port (default: "++show defport++")") 29 | ] 30 | 31 | webmode :: Mode [([Char], [Char])] 32 | webmode = (mode "hledger-web" [("command","web")] 33 | "start serving the hledger web interface" 34 | mainargsflag []){ 35 | modeGroupFlags = Group { 36 | groupUnnamed = webflags 37 | ,groupHidden = [flagNone ["binary-filename"] (setboolopt "binary-filename") "show the download filename for this executable, and exit"] 38 | ,groupNamed = [(generalflagstitle, generalflags1)] 39 | } 40 | ,modeHelpSuffix=[ 41 | -- "Reads your ~/.hledger.journal file, or another specified by $LEDGER_FILE or -f, and starts the full-window curses ui." 42 | ] 43 | } 44 | 45 | -- hledger-web options, used in hledger-web and above 46 | data WebOpts = WebOpts { 47 | server_ :: Bool 48 | ,base_url_ :: String 49 | ,port_ :: Int 50 | ,cliopts_ :: CliOpts 51 | } deriving (Show) 52 | 53 | defwebopts :: WebOpts 54 | defwebopts = WebOpts 55 | def 56 | def 57 | def 58 | def 59 | 60 | -- instance Default WebOpts where def = defwebopts 61 | 62 | toWebOpts :: RawOpts -> IO WebOpts 63 | toWebOpts rawopts = do 64 | cliopts <- toCliOpts rawopts 65 | let p = fromMaybe defport $ maybeintopt "port" rawopts 66 | return defwebopts { 67 | port_ = p 68 | ,server_ = boolopt "server" rawopts 69 | ,base_url_ = maybe (defbaseurl p) stripTrailingSlash $ maybestringopt "base-url" rawopts 70 | ,cliopts_ = cliopts 71 | } 72 | where 73 | stripTrailingSlash = reverse . dropWhile (=='/') . reverse -- yesod don't like it 74 | 75 | checkWebOpts :: WebOpts -> IO WebOpts 76 | checkWebOpts opts = do 77 | _ <- checkCliOpts $ cliopts_ opts 78 | return opts 79 | 80 | getHledgerWebOpts :: IO WebOpts 81 | getHledgerWebOpts = processArgs webmode >>= return . decodeRawOpts >>= toWebOpts >>= checkWebOpts 82 | 83 | -------------------------------------------------------------------------------- /hledger-web/Hledger/Web/Main.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | 3 | hledger-web - a hledger add-on providing a web interface. 4 | Copyright (c) 2007-2012 Simon Michael 5 | Released under GPL version 3 or later. 6 | 7 | -} 8 | 9 | module Hledger.Web.Main 10 | where 11 | 12 | -- yesod scaffold imports 13 | import Prelude (IO) 14 | import Yesod.Default.Config --(fromArgs) 15 | -- import Yesod.Default.Main (defaultMain) 16 | import Settings -- (parseExtra) 17 | import Application (makeApplication) 18 | import Data.Conduit.Network (HostPreference(HostIPv4)) 19 | import Network.Wai.Handler.Warp (runSettings, defaultSettings, settingsPort) 20 | import Network.Wai.Handler.Launch (runUrlPort) 21 | -- 22 | import Prelude hiding (putStrLn) 23 | import Control.Monad (when) 24 | import Data.Text (pack) 25 | import System.Exit (exitSuccess) 26 | import System.IO (hFlush, stdout) 27 | import Text.Printf 28 | 29 | import Hledger 30 | import Hledger.Utils.UTF8IOCompat (putStrLn) 31 | import Hledger.Cli hiding (progname,prognameandversion) 32 | import Hledger.Web.Options 33 | 34 | 35 | main :: IO () 36 | main = do 37 | opts <- getHledgerWebOpts 38 | when (debug_ $ cliopts_ opts) $ printf "%s\n" prognameandversion >> printf "opts: %s\n" (show opts) 39 | runWith opts 40 | 41 | runWith :: WebOpts -> IO () 42 | runWith opts 43 | | "help" `in_` (rawopts_ $ cliopts_ opts) = putStr (showModeHelp webmode) >> exitSuccess 44 | | "version" `in_` (rawopts_ $ cliopts_ opts) = putStrLn prognameandversion >> exitSuccess 45 | | "binary-filename" `in_` (rawopts_ $ cliopts_ opts) = putStrLn (binaryfilename progname) 46 | | otherwise = do 47 | requireJournalFileExists =<< journalFilePathFromOpts (cliopts_ opts) 48 | withJournalDo' opts web 49 | 50 | withJournalDo' :: WebOpts -> (WebOpts -> Journal -> IO ()) -> IO () 51 | withJournalDo' opts cmd = do 52 | journalFilePathFromOpts (cliopts_ opts) >>= readJournalFile Nothing Nothing >>= 53 | either error' (cmd opts . journalApplyAliases (aliasesFromOpts $ cliopts_ opts)) 54 | 55 | -- | The web command. 56 | web :: WebOpts -> Journal -> IO () 57 | web opts j = do 58 | d <- getCurrentDay 59 | let j' = filterJournalTransactions (queryFromOpts d $ reportopts_ $ cliopts_ opts) j 60 | p = port_ opts 61 | u = base_url_ opts 62 | _ <- printf "Starting web app on port %d with base url %s\n" p u 63 | app <- makeApplication opts j' AppConfig{appEnv = Development 64 | ,appPort = p 65 | ,appRoot = pack u 66 | ,appHost = HostIPv4 67 | ,appExtra = Extra "" Nothing 68 | } 69 | if server_ opts 70 | then do 71 | putStrLn "Press ctrl-c to quit" 72 | hFlush stdout 73 | runSettings defaultSettings{settingsPort=p} app 74 | else do 75 | putStrLn "Starting web browser if possible" 76 | putStrLn "Web app will auto-exit after a few minutes with no browsers (or press ctrl-c)" 77 | hFlush stdout 78 | runUrlPort p "" app 79 | -------------------------------------------------------------------------------- /tests/amount-layout-vertical.test: -------------------------------------------------------------------------------- 1 | # amount layout tests, using default vertical layout 2 | # 1. print 3 | hledgerdev -f - print 4 | <<< 5 | 2010/1/1 6 | a EUR 1 ; a euro 7 | b USD 1 ; a dollar 8 | c ; a euro and a dollar 9 | >>> 10 | 2010/01/01 11 | a EUR 1 ; a euro 12 | b USD 1 ; a dollar 13 | EUR -1 14 | c USD -1 ; a euro and a dollar 15 | 16 | >>>=0 17 | 18 | # 2. register 19 | hledgerdev -f - register 20 | <<< 21 | 2010/1/1 22 | a EUR 1 ; a euro 23 | b USD 1 ; a dollar 24 | c ; a euro and a dollar 25 | >>> 26 | 2010/01/01 a EUR 1 EUR 1 27 | EUR 1 28 | b USD 1 USD 1 29 | EUR -1 30 | c USD -1 0 31 | >>>=0 32 | 33 | # 3. balance 34 | hledgerdev -f - balance 35 | <<< 36 | 2010/1/1 37 | a EUR 1 ; a euro 38 | b USD 1 ; a dollar 39 | c ; a euro and a dollar 40 | >>> 41 | EUR 1 a 42 | USD 1 b 43 | EUR -1 44 | USD -1 c 45 | -------------------- 46 | 0 47 | >>>=0 48 | 49 | # 4. mixed amounts with prices 50 | # XXX 51 | # hledgerdev -f - print 52 | # <<< 53 | # 2010/1/1 54 | # a EUR 1 @ USD 1.1 ; a euro 55 | # b USD 1 ; a dollar 56 | # c ; a euro and a dollar 57 | # >>> 58 | # 2010/01/01 59 | # a EUR 1 @ USD 1.1 ; a euro 60 | # b USD 1 ; a dollar 61 | # EUR -1 @ USD 1.1 62 | # c USD -1 ; a euro and a dollar 63 | # 64 | ## 65 | # 2010/01/01 66 | # a EUR 1 @ USD 1.1 ; a euro 67 | # b USD 1.0 ; a dollar 68 | # c USD -2.1 ; a euro and a dollar 69 | # 70 | #>>>=0 71 | # 72 | # # 73 | # hledgerdev -f - register 74 | # <<< 75 | # 2010/1/1 76 | # a EUR 1 @ USD 1.1 ; a euro 77 | # b USD 1 ; a dollar 78 | # c ; a euro and a dollar 79 | # >>> 80 | # 2010/01/01 a EUR 1 EUR 1 81 | # EUR 1 82 | # b USD 1.0 USD 1.0 83 | # EUR 1 84 | # c USD -2.1 USD -1.1 85 | #>>>=0 86 | # 87 | # # 88 | # hledgerdev -f - balance 89 | # <<< 90 | # 2010/1/1 91 | # a EUR 1 @ USD 1.1 ; a euro 92 | # b USD 1 ; a dollar 93 | # c ; a euro and a dollar 94 | # >>> 95 | # EUR 1 a 96 | # USD 1.0 b 97 | # USD -2.1 c 98 | # -------------------- 99 | # EUR 1 100 | # USD -1.1 101 | -------------------------------------------------------------------------------- /hledger-web/Settings.hs: -------------------------------------------------------------------------------- 1 | -- | Settings are centralized, as much as possible, into this file. This 2 | -- includes database connection settings, static file locations, etc. 3 | -- In addition, you can configure a number of different aspects of Yesod 4 | -- by overriding methods in the Yesod typeclass. That instance is 5 | -- declared in the Foundation.hs file. 6 | module Settings where 7 | 8 | import Prelude 9 | import Text.Shakespeare.Text (st) 10 | import Language.Haskell.TH.Syntax 11 | import Yesod.Default.Config 12 | import Yesod.Default.Util 13 | import Data.Text (Text) 14 | import Data.Yaml 15 | import Control.Applicative 16 | import Settings.Development 17 | import Data.Default (def) 18 | import Text.Hamlet 19 | 20 | import Text.Printf (printf) 21 | 22 | 23 | hledgerorgurl, manualurl :: String 24 | hledgerorgurl = "http://hledger.org" 25 | manualurl = hledgerorgurl++"/MANUAL.html" 26 | 27 | -- | The default TCP port to listen on. May be overridden with --port. 28 | defport :: Int 29 | defport = 5000 30 | 31 | defbaseurl :: Int -> String 32 | defbaseurl port = printf "http://localhost:%d" port 33 | 34 | 35 | 36 | 37 | -- Static setting below. Changing these requires a recompile 38 | 39 | -- | The location of static files on your system. This is a file system 40 | -- path. The default value works properly with your scaffolded site. 41 | staticDir :: FilePath 42 | staticDir = "static" 43 | 44 | -- | The base URL for your static files. As you can see by the default 45 | -- value, this can simply be "static" appended to your application root. 46 | -- A powerful optimization can be serving static files from a separate 47 | -- domain name. This allows you to use a web server optimized for static 48 | -- files, more easily set expires and cache values, and avoid possibly 49 | -- costly transference of cookies on static files. For more information, 50 | -- please see: 51 | -- http://code.google.com/speed/page-speed/docs/request.html#ServeFromCookielessDomain 52 | -- 53 | -- If you change the resource pattern for StaticR in Foundation.hs, you will 54 | -- have to make a corresponding change here. 55 | -- 56 | -- To see how this value is used, see urlRenderOverride in Foundation.hs 57 | staticRoot :: AppConfig DefaultEnv x -> Text 58 | staticRoot conf = [st|#{appRoot conf}/static|] 59 | 60 | -- | Settings for 'widgetFile', such as which template languages to support and 61 | -- default Hamlet settings. 62 | widgetFileSettings :: WidgetFileSettings 63 | widgetFileSettings = def 64 | { wfsHamletSettings = defaultHamletSettings 65 | { hamletNewlines = AlwaysNewlines 66 | } 67 | } 68 | 69 | -- The rest of this file contains settings which rarely need changing by a 70 | -- user. 71 | 72 | widgetFile :: String -> Q Exp 73 | widgetFile = (if development then widgetFileReload 74 | else widgetFileNoReload) 75 | widgetFileSettings 76 | 77 | data Extra = Extra 78 | { extraCopyright :: Text 79 | , extraAnalytics :: Maybe Text -- ^ Google Analytics 80 | } deriving Show 81 | 82 | parseExtra :: DefaultEnv -> Object -> Parser Extra 83 | parseExtra _ o = Extra 84 | <$> o .: "copyright" 85 | <*> o .:? "analytics" 86 | -------------------------------------------------------------------------------- /hledger-web/deploy/Procfile: -------------------------------------------------------------------------------- 1 | # Free deployment to Heroku. 2 | # 3 | # !! Warning: You must use a 64 bit machine to compile !! 4 | # 5 | # This could mean using a virtual machine. Give your VM as much memory as you can to speed up linking. 6 | # 7 | # Basic Yesod setup: 8 | # 9 | # * Move this file out of the deploy directory and into your root directory 10 | # 11 | # mv deploy/Procfile ./ 12 | # 13 | # * Create an empty package.json 14 | # echo '{ "name": "hledger-web", "version": "0.0.1", "dependencies": {} }' >> package.json 15 | # 16 | # Postgresql Yesod setup: 17 | # 18 | # * add dependencies on the "heroku", "aeson" and "unordered-containers" packages in your cabal file 19 | # 20 | # * add code in Application.hs to use the heroku package and load the connection parameters. 21 | # The below works for Postgresql. 22 | # 23 | # import Data.HashMap.Strict as H 24 | # import Data.Aeson.Types as AT 25 | # #ifndef DEVELOPMENT 26 | # import qualified Web.Heroku 27 | # #endif 28 | # 29 | # 30 | # 31 | # makeFoundation :: AppConfig DefaultEnv Extra -> Logger -> IO App 32 | # makeFoundation conf setLogger = do 33 | # manager <- newManager def 34 | # s <- staticSite 35 | # hconfig <- loadHerokuConfig 36 | # dbconf <- withYamlEnvironment "config/postgresql.yml" (appEnv conf) 37 | # (Database.Persist.Store.loadConfig . combineMappings hconfig) >>= 38 | # Database.Persist.Store.applyEnv 39 | # p <- Database.Persist.Store.createPoolConfig (dbconf :: Settings.PersistConfig) 40 | # Database.Persist.Store.runPool dbconf (runMigration migrateAll) p 41 | # return $ App conf setLogger s p manager dbconf 42 | # 43 | # #ifndef DEVELOPMENT 44 | # canonicalizeKey :: (Text, val) -> (Text, val) 45 | # canonicalizeKey ("dbname", val) = ("database", val) 46 | # canonicalizeKey pair = pair 47 | # 48 | # toMapping :: [(Text, Text)] -> AT.Value 49 | # toMapping xs = AT.Object $ M.fromList $ map (\(key, val) -> (key, AT.String val)) xs 50 | # #endif 51 | # 52 | # combineMappings :: AT.Value -> AT.Value -> AT.Value 53 | # combineMappings (AT.Object m1) (AT.Object m2) = AT.Object $ m1 `M.union` m2 54 | # combineMappings _ _ = error "Data.Object is not a Mapping." 55 | # 56 | # loadHerokuConfig :: IO AT.Value 57 | # loadHerokuConfig = do 58 | # #ifdef DEVELOPMENT 59 | # return $ AT.Object M.empty 60 | # #else 61 | # Web.Heroku.dbConnParams >>= return . toMapping . map canonicalizeKey 62 | # #endif 63 | 64 | 65 | 66 | # Heroku setup: 67 | # Find the Heroku guide. Roughly: 68 | # 69 | # * sign up for a heroku account and register your ssh key 70 | # * create a new application on the *cedar* stack 71 | # 72 | # * make your Yesod project the git repository for that application 73 | # * create a deploy branch 74 | # 75 | # git checkout -b deploy 76 | # 77 | # Repeat these steps to deploy: 78 | # * add your web executable binary (referenced below) to the git repository 79 | # 80 | # git checkout deploy 81 | # git add ./dist/build/hledger-web/hledger-web 82 | # git commit -m deploy 83 | # 84 | # * push to Heroku 85 | # 86 | # git push heroku deploy:master 87 | 88 | 89 | # Heroku configuration that runs your app 90 | web: ./dist/build/hledger-web/hledger-web production -p $PORT 91 | -------------------------------------------------------------------------------- /tools/doctest.hs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env runhaskell 2 | {- | 3 | Extract (shell) tests from haddock comments in Haskell code, run them and 4 | verify expected output, like Python's doctest system. 5 | 6 | Here, a doctest is a haddock literal block whose first line begins with a 7 | $ (leading whitespace ignored). The rest of the line is a shell command 8 | and the remaining lines are the expected output. 9 | 10 | Usage example: $ doctest.hs doctest.hs 11 | 12 | Doctest examples: 13 | 14 | @ 15 | $ ls doctest.hs 16 | This doctest will fail. 17 | @ 18 | 19 | @ 20 | $ ls doctest.hs 21 | doctest.hs 22 | @ 23 | 24 | Issues: 25 | 26 | After writing this I found the doctest on hackage; that one runs haskell 27 | expressions in comments, converting them to hunit tests. We might add this 28 | to that, and/or add this to hledger's built-in test runner. 29 | 30 | Error output seems to vary depending on whether things are compiled, eg: 31 | hledger: parse error at (line 1, column 4) 32 | vs: 33 | "-" (line 2, column 1) 34 | ledger-style functional tests may be more useful for this, see functest.hs. 35 | 36 | -} 37 | 38 | module Main where 39 | import Data.List (isPrefixOf) 40 | import System (getArgs) 41 | import System.Exit (exitFailure, exitWith, ExitCode(ExitSuccess)) -- base 3 compatible 42 | import System.IO (hGetContents, hPutStr, hPutStrLn, stderr) 43 | import System.Process (runInteractiveCommand, waitForProcess) 44 | import Text.Printf (printf) 45 | 46 | main = do 47 | f <- head `fmap` getArgs 48 | s <- readFile f 49 | let tests = doctests s 50 | putStrLn $ printf "Running %d doctests from %s" (length tests) f 51 | ok <- mapM runShellDocTest $ doctests s 52 | if any not ok then exitFailure else exitWith ExitSuccess 53 | 54 | runShellDocTest :: String -> IO Bool 55 | runShellDocTest s = do 56 | let (cmd, expected) = splitDocTest s 57 | putStr $ printf "Testing: %s .. " cmd 58 | (_, out, _, h) <- runInteractiveCommand cmd 59 | exit <- waitForProcess h 60 | output <- hGetContents out 61 | if exit == ExitSuccess 62 | then 63 | if output == expected 64 | then do 65 | putStrLn "ok" 66 | return True 67 | else do 68 | hPutStr stderr $ printf "FAILED\nExpected:\n%sGot:\n%s" expected output 69 | return False 70 | else do 71 | hPutStrLn stderr $ printf "ERROR: %s" (show exit) 72 | return False 73 | 74 | splitDocTest s = (strip $ drop 1 $ strip $ head ls, unlines $ tail ls) 75 | where ls = lines s 76 | 77 | -- extract doctests from haskell source code 78 | doctests :: String -> [String] 79 | doctests s = filter isDocTest $ haddockLiterals s 80 | where 81 | isDocTest = (("$" `isPrefixOf`) . dropws) . head . lines 82 | 83 | -- extract haddock literal blocks from haskell source code 84 | haddockLiterals :: String -> [String] 85 | haddockLiterals "" = [] 86 | haddockLiterals s | null lit = [] 87 | | otherwise = lit : haddockLiterals rest 88 | where 89 | ls = drop 1 $ dropWhile (not . isLiteralBoundary) $ lines s 90 | lit = unlines $ takeWhile (not . isLiteralBoundary) ls 91 | rest = unlines $ drop 1 $ dropWhile (not . isLiteralBoundary) ls 92 | isLiteralBoundary = (== "@") . strip 93 | 94 | strip = dropws . reverse . dropws . reverse 95 | dropws = dropWhile (`elem` " \t") 96 | -------------------------------------------------------------------------------- /tests/precision.test: -------------------------------------------------------------------------------- 1 | # http://code.google.com/p/hledger/issues/detail?id=23 2 | # 3 | # 1. original test case, this should balance since price precisions do 4 | # not affect the canonical display precisions used for display and balancing 5 | hledgerdev -f - print 6 | <<< 7 | 2010/1/1 x 8 | A 55.3653 C @ 30.92189512 D 9 | A -1712 D 10 | >>> 11 | 2010/01/01 x 12 | A 55.3653 C @ 30.92189512 D 13 | A -1712 D 14 | 15 | >>>=0 16 | 17 | ## 1b. here $'s canonical display precision should be 2 not 4 18 | ## XXX no, because the inferred amount $1.0049 is observed 19 | # hledgerdev -f - print --cost 20 | # <<< 21 | # 2010/1/1 22 | # a $0.00 23 | # a 1C @ $1.0049 24 | # a 25 | # >>> 26 | # 2010/01/01 27 | # a 0 28 | # a $1.00 29 | # a $-1.00 30 | # 31 | # >>>=0 32 | 33 | # 2. and here the price should be printed with its original precision, not 34 | # the canonical display precision 35 | hledgerdev -f - print 36 | <<< 37 | 2010/1/1 38 | a $0.00 39 | a 1C @ $1.0049 40 | a 41 | >>> 42 | 2010/01/01 43 | a 0 44 | a 1C @ $1.0049 45 | a $-1.0049 46 | 47 | >>>=0 48 | 49 | # 3. with $'s display precision at 3 or more, this txn should not balance. 50 | # The error message shows the difference with full precision. 51 | hledgerdev -f - balance --no-total --cost --empty 52 | <<< 53 | 2010/1/1 54 | a 1C @ $1.0049 55 | a $-1.000 56 | >>>2 /off by \$0.0049/ 57 | >>>= 1 58 | 59 | # 4. with $'s display precision at 2 or less, this txn should balance 60 | hledgerdev -f - balance --no-total --cost --empty 61 | <<< 62 | 2010/1/1 63 | a 1C @ $1.0049 64 | a $-1.00 65 | >>> 66 | 0 a 67 | >>>=0 68 | 69 | # 5. avamk's 2011/1/19 example 70 | hledgerdev -f - balance --cost 71 | <<< 72 | 2001/01/01 * ACME fund 73 | assets:investment:ACME 203.890 ACME @ $16.02 74 | equity:opening balances 75 | >>> 76 | $3266.32 assets:investment:ACME 77 | $-3266.32 equity:opening balances 78 | -------------------- 79 | 0 80 | >>>=0 81 | # hledger 0.14pre: precision=2, presumably from price 82 | # $3266.32 assets:investment:ACME 83 | # $-3266.32 equity:opening balances 84 | #-------------------- 85 | # $0.00 86 | # 87 | # ledger "2.6.0.90" with -s: full precision, ignores price 88 | # $3266.3178 assets:investment:ACME 89 | # $-3266.3178 equity:opening balances 90 | # 91 | # ledger 3: precision=0, uses default, ignores price 92 | # $3266 assets:investment:ACME 93 | # $-3266 equity:opening balances 94 | #-------------------- 95 | # 0 96 | ## 6. with a default commodity.. XXX should observe it 97 | 98 | hledgerdev -f - balance --cost 99 | <<< 100 | D $1000.0 101 | 2001/01/01 * ACME fund 102 | assets:investment:ACME 203.890 ACME @ $16.02 103 | equity:opening balances 104 | >>> 105 | $3266.32 assets:investment:ACME 106 | $-3266.32 equity:opening balances 107 | -------------------- 108 | 0 109 | >>>=0 110 | ### hledger 0.14pre: precision=2, presumably from price, ignores D 111 | ### $3266.32 assets:investment:ACME 112 | ### $-3266.32 equity:opening balances 113 | ###-------------------- 114 | ### $0.00 115 | ### 116 | ### ledger "2.6.0.90" with -s: full precision, ignores price and D 117 | ### $3266.3178 assets:investment:ACME 118 | ### $-3266.3178 equity:opening balances 119 | ### 120 | ### ledger 3: precision=1, ignores price, observes D 121 | ### $3266.3 assets:investment:ACME 122 | ### $-3266.3 equity:opening balances 123 | ###-------------------- 124 | ### 0 125 | -------------------------------------------------------------------------------- /hledger-lib/Hledger/Data/Ledger.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | 3 | A 'Ledger' is derived from a 'Journal' by applying a filter specification 4 | to select 'Transaction's and 'Posting's of interest. It contains the 5 | filtered journal and knows the resulting chart of accounts, account 6 | balances, and postings in each account. 7 | 8 | -} 9 | 10 | module Hledger.Data.Ledger 11 | where 12 | import qualified Data.Map as M 13 | import Safe (headDef) 14 | import Test.HUnit 15 | import Text.Printf 16 | 17 | import Hledger.Data.Types 18 | import Hledger.Data.Account 19 | import Hledger.Data.Journal 20 | import Hledger.Data.Posting 21 | import Hledger.Query 22 | 23 | 24 | instance Show Ledger where 25 | show l = printf "Ledger with %d transactions, %d accounts\n" --"%s" 26 | (length (jtxns $ ljournal l) + 27 | length (jmodifiertxns $ ljournal l) + 28 | length (jperiodictxns $ ljournal l)) 29 | (length $ ledgerAccountNames l) 30 | -- (showtree $ ledgerAccountNameTree l) 31 | 32 | nullledger :: Ledger 33 | nullledger = Ledger { 34 | ljournal = nulljournal, 35 | laccounts = [] 36 | } 37 | 38 | -- | Filter a journal's transactions with the given query, then derive a 39 | -- ledger containing the chart of accounts and balances. If the query 40 | -- includes a depth limit, that will affect the ledger's journal but not 41 | -- the account tree. 42 | ledgerFromJournal :: Query -> Journal -> Ledger 43 | ledgerFromJournal q j = nullledger{ljournal=j'', laccounts=as} 44 | where 45 | (q',depthq) = (filterQuery (not . queryIsDepth) q, filterQuery queryIsDepth q) 46 | j' = filterJournalPostings q' j 47 | as = accountsFromPostings $ journalPostings j' 48 | j'' = filterJournalPostings depthq j' 49 | 50 | -- | List a ledger's account names. 51 | ledgerAccountNames :: Ledger -> [AccountName] 52 | ledgerAccountNames = drop 1 . map aname . laccounts 53 | 54 | -- | Get the named account from a ledger. 55 | ledgerAccount :: Ledger -> AccountName -> Maybe Account 56 | ledgerAccount l a = lookupAccount a $ laccounts l 57 | 58 | -- | Get this ledger's root account, which is a dummy "root" account 59 | -- above all others. This should always be first in the account list, 60 | -- if somehow not this returns a null account. 61 | ledgerRootAccount :: Ledger -> Account 62 | ledgerRootAccount = headDef nullacct . laccounts 63 | 64 | -- | List a ledger's top-level accounts (the ones below the root), in tree order. 65 | ledgerTopAccounts :: Ledger -> [Account] 66 | ledgerTopAccounts = asubs . head . laccounts 67 | 68 | -- | List a ledger's bottom-level (subaccount-less) accounts, in tree order. 69 | ledgerLeafAccounts :: Ledger -> [Account] 70 | ledgerLeafAccounts = filter (null.asubs) . laccounts 71 | 72 | -- | Accounts in ledger whose name matches the pattern, in tree order. 73 | ledgerAccountsMatching :: [String] -> Ledger -> [Account] 74 | ledgerAccountsMatching pats = filter (matchpats pats . aname) . laccounts 75 | 76 | -- | List a ledger's postings, in the order parsed. 77 | ledgerPostings :: Ledger -> [Posting] 78 | ledgerPostings = journalPostings . ljournal 79 | 80 | -- | The (fully specified) date span containing all the ledger's (filtered) transactions, 81 | -- or DateSpan Nothing Nothing if there are none. 82 | ledgerDateSpan :: Ledger -> DateSpan 83 | ledgerDateSpan = postingsDateSpan . ledgerPostings 84 | 85 | -- | All commodities used in this ledger. 86 | ledgerCommodities :: Ledger -> [Commodity] 87 | ledgerCommodities = M.keys . jcommoditystyles . ljournal 88 | 89 | 90 | tests_ledgerFromJournal = [ 91 | "ledgerFromJournal" ~: do 92 | assertEqual "" (0) (length $ ledgerPostings $ ledgerFromJournal Any nulljournal) 93 | assertEqual "" (11) (length $ ledgerPostings $ ledgerFromJournal Any samplejournal) 94 | assertEqual "" (6) (length $ ledgerPostings $ ledgerFromJournal (Depth 2) samplejournal) 95 | ] 96 | 97 | tests_Hledger_Data_Ledger = TestList $ 98 | tests_ledgerFromJournal 99 | -------------------------------------------------------------------------------- /hledger/Hledger/Cli/Register.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | 3 | A ledger-compatible @register@ command. 4 | 5 | -} 6 | 7 | module Hledger.Cli.Register ( 8 | register 9 | ,postingsReportAsText 10 | -- ,showPostingWithBalanceForVty 11 | ,tests_Hledger_Cli_Register 12 | ) where 13 | 14 | import Data.List 15 | import Data.Maybe 16 | import Test.HUnit 17 | import Text.Printf 18 | 19 | import Hledger 20 | import Prelude hiding (putStr) 21 | import Hledger.Utils.UTF8IOCompat (putStr) 22 | import Hledger.Cli.Options 23 | 24 | 25 | -- | Print a (posting) register report. 26 | register :: CliOpts -> Journal -> IO () 27 | register opts@CliOpts{reportopts_=ropts} j = do 28 | d <- getCurrentDay 29 | putStr $ postingsReportAsText opts $ postingsReport ropts (queryFromOpts d ropts) j 30 | 31 | -- | Render a register report as plain text suitable for console output. 32 | postingsReportAsText :: CliOpts -> PostingsReport -> String 33 | postingsReportAsText opts = unlines . map (postingsReportItemAsText opts) . snd 34 | 35 | tests_postingsReportAsText = [ 36 | "postingsReportAsText" ~: do 37 | -- "unicode in register layout" ~: do 38 | j <- readJournal' 39 | "2009/01/01 * медвежья шкура\n расходы:покупки 100\n актив:наличные\n" 40 | let opts = defreportopts 41 | (postingsReportAsText defcliopts $ postingsReport opts (queryFromOpts (parsedate "2008/11/26") opts) j) `is` unlines 42 | ["2009/01/01 медвежья шкура расходы:покупки 100 100" 43 | ," актив:наличные -100 0"] 44 | ] 45 | 46 | -- | Render one register report line item as plain text. Layout is like so: 47 | -- @ 48 | -- <----------------------------- width (default: 80) ----------------------------> 49 | -- date (10) description (50%) account (50%) amount (12) balance (12) 50 | -- DDDDDDDDDD dddddddddddddddddddd aaaaaaaaaaaaaaaaaaa AAAAAAAAAAAA AAAAAAAAAAAA 51 | -- 52 | -- date and description are shown for the first posting of a transaction only. 53 | -- @ 54 | postingsReportItemAsText :: CliOpts -> PostingsReportItem -> String 55 | postingsReportItemAsText opts (mdate, mdesc, p, b) = 56 | concatTopPadded [date, " ", desc, " ", acct, " ", amt, " ", bal] 57 | where 58 | totalwidth = case widthFromOpts opts of 59 | Left _ -> defaultWidth -- shouldn't happen 60 | Right (TotalWidth (Width w)) -> w 61 | Right (TotalWidth Auto) -> defaultWidth -- XXX 62 | Right (FieldWidths _) -> defaultWidth -- XXX 63 | datewidth = 10 64 | amtwidth = 12 65 | balwidth = 12 66 | remaining = totalwidth - (datewidth + 1 + 2 + amtwidth + 2 + balwidth) 67 | (descwidth, acctwidth) | even r = (r', r') 68 | | otherwise = (r', r'+1) 69 | where r = remaining - 2 70 | r' = r `div` 2 71 | date = maybe (replicate datewidth ' ') (printf ("%-"++show datewidth++"s") . showDate) mdate 72 | desc = maybe (replicate descwidth ' ') (printf ("%-"++show descwidth++"s") . take descwidth . elideRight descwidth) mdesc 73 | acct = printf ("%-"++(show acctwidth)++"s") a 74 | where 75 | a = bracket $ elideAccountName awidth $ paccount p 76 | (bracket, awidth) = case ptype p of 77 | BalancedVirtualPosting -> (\s -> "["++s++"]", acctwidth-2) 78 | VirtualPosting -> (\s -> "("++s++")", acctwidth-2) 79 | _ -> (id,acctwidth) 80 | amt = padleft amtwidth $ showMixedAmountWithoutPrice $ pamount p 81 | bal = padleft balwidth $ showMixedAmountWithoutPrice b 82 | 83 | -- XXX 84 | -- showPostingWithBalanceForVty showtxninfo p b = postingsReportItemAsText defreportopts $ mkpostingsReportItem showtxninfo p b 85 | 86 | tests_Hledger_Cli_Register :: Test 87 | tests_Hledger_Cli_Register = TestList 88 | tests_postingsReportAsText 89 | -------------------------------------------------------------------------------- /hledger-lib/Hledger/Read/TimelogReader.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | 3 | A reader for the timelog file format generated by timeclock.el 4 | (). Example: 5 | 6 | @ 7 | i 2007\/03\/10 12:26:00 hledger 8 | o 2007\/03\/10 17:26:02 9 | @ 10 | 11 | From timeclock.el 2.6: 12 | 13 | @ 14 | A timelog contains data in the form of a single entry per line. 15 | Each entry has the form: 16 | 17 | CODE YYYY/MM/DD HH:MM:SS [COMMENT] 18 | 19 | CODE is one of: b, h, i, o or O. COMMENT is optional when the code is 20 | i, o or O. The meanings of the codes are: 21 | 22 | b Set the current time balance, or \"time debt\". Useful when 23 | archiving old log data, when a debt must be carried forward. 24 | The COMMENT here is the number of seconds of debt. 25 | 26 | h Set the required working time for the given day. This must 27 | be the first entry for that day. The COMMENT in this case is 28 | the number of hours in this workday. Floating point amounts 29 | are allowed. 30 | 31 | i Clock in. The COMMENT in this case should be the name of the 32 | project worked on. 33 | 34 | o Clock out. COMMENT is unnecessary, but can be used to provide 35 | a description of how the period went, for example. 36 | 37 | O Final clock out. Whatever project was being worked on, it is 38 | now finished. Useful for creating summary reports. 39 | @ 40 | 41 | -} 42 | 43 | module Hledger.Read.TimelogReader ( 44 | -- * Reader 45 | reader, 46 | -- * Tests 47 | tests_Hledger_Read_TimelogReader 48 | ) 49 | where 50 | import Control.Monad 51 | import Control.Monad.Error 52 | import Test.HUnit 53 | import Text.ParserCombinators.Parsec hiding (parse) 54 | import System.FilePath 55 | 56 | import Hledger.Data 57 | -- XXX too much reuse ? 58 | import Hledger.Read.JournalReader ( 59 | directive, historicalpricedirective, defaultyeardirective, emptyline, datetime, 60 | parseJournalWith, getParentAccount 61 | ) 62 | import Hledger.Utils 63 | 64 | 65 | reader :: Reader 66 | reader = Reader format detect parse 67 | 68 | format :: String 69 | format = "timelog" 70 | 71 | -- | Does the given file path and data provide timeclock.el's timelog format ? 72 | detect :: FilePath -> String -> Bool 73 | detect f _ = takeExtension f == '.':format 74 | 75 | -- | Parse and post-process a "Journal" from timeclock.el's timelog 76 | -- format, saving the provided file path and the current time, or give an 77 | -- error. 78 | parse :: Maybe FilePath -> FilePath -> String -> ErrorT String IO Journal 79 | parse _ = -- trace ("running "++format++" reader") . 80 | parseJournalWith timelogFile 81 | 82 | timelogFile :: GenParser Char JournalContext (JournalUpdate,JournalContext) 83 | timelogFile = do items <- many timelogItem 84 | eof 85 | ctx <- getState 86 | return (liftM (foldr (.) id) $ sequence items, ctx) 87 | where 88 | -- As all ledger line types can be distinguished by the first 89 | -- character, excepting transactions versus empty (blank or 90 | -- comment-only) lines, can use choice w/o try 91 | timelogItem = choice [ directive 92 | , liftM (return . addHistoricalPrice) historicalpricedirective 93 | , defaultyeardirective 94 | , emptyline >> return (return id) 95 | , liftM (return . addTimeLogEntry) timelogentry 96 | ] "timelog entry, or default year or historical price directive" 97 | 98 | -- | Parse a timelog entry. 99 | timelogentry :: GenParser Char JournalContext TimeLogEntry 100 | timelogentry = do 101 | code <- oneOf "bhioO" 102 | many1 spacenonewline 103 | datetime <- datetime 104 | comment <- optionMaybe (many1 spacenonewline >> liftM2 (++) getParentAccount restofline) 105 | return $ TimeLogEntry (read [code]) datetime (maybe "" rstrip comment) 106 | 107 | tests_Hledger_Read_TimelogReader = TestList [ 108 | ] 109 | 110 | -------------------------------------------------------------------------------- /site/css/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | overflow-y:scroll; 3 | } 4 | 5 | body { 6 | margin: auto; 7 | max-width: 42em; 8 | margin-top: 1.0em; 9 | background-color: #fff; 10 | font-family: "helvetica","arial", "sans serif"; 11 | } 12 | 13 | div#navigation { 14 | /* text-align: center; */ 15 | /* border-bottom: 4px solid #226600; */ 16 | } 17 | 18 | div#navigation a { 19 | color: white; 20 | font-weight:bold; 21 | font-family:sans-serif; 22 | /* font-size:small; */ 23 | text-decoration: none; 24 | background-color: #226600; 25 | padding: 3px 10px 3px 10px; 26 | margin: 0 5px 0 0; 27 | border-radius:.3em; 28 | } 29 | 30 | div#navigation a.offsite { 31 | background-color: #113300; 32 | } 33 | 34 | div#navigation a:active { 35 | position:relative; 36 | top:1px; 37 | } 38 | 39 | #footer { 40 | padding-top: 1em; 41 | /* font-size: 70%; */ 42 | color: gray; 43 | text-align: center; 44 | } 45 | 46 | code { 47 | color: #226600; 48 | font-weight:bold; 49 | } 50 | 51 | #container { 52 | margin: 0 auto; 53 | width: 700px; 54 | } 55 | h1 { 56 | font-size: 2.5em; 57 | /* margin-top: 0.7em; */ 58 | /* margin-bottom: 0.7em; */ 59 | } 60 | h2 { 61 | font-size: 1.8em; 62 | margin-top: 1.5em; 63 | /* margin-bottom: 0.6em; */ 64 | } 65 | h3 { 66 | font-size: 1.6em; 67 | /* margin-top: 0.7em; */ 68 | /* margin-bottom: 0.5em; */ 69 | } 70 | h4 { 71 | font-size: 1.4em; 72 | /* margin-top: 0.7em; */ 73 | /* margin-bottom: 0.4em; */ 74 | } 75 | h5 { 76 | font-size: 1.2em; 77 | margin-top: 1em; 78 | /* margin-bottom: 0.3em; */ 79 | } 80 | h6 { 81 | font-size: 1em; 82 | margin-top: 0.5em; 83 | /* margin-bottom: 0.2em; */ 84 | } 85 | h7 { 86 | font-size: 1em; 87 | margin-top: 0.5em; 88 | /* margin-bottom: 0.2em; */ 89 | } 90 | h8 { 91 | font-size: 1em; 92 | margin-top: 0.5em; 93 | /* margin-bottom: 0.2em; */ 94 | } 95 | h1, h2, h3, h4, h5, h6, h7, h8 { 96 | margin-bottom: 0em; 97 | color:black; 98 | } 99 | h1 a, h2 a, h3 a, h4 a, h5 a, h6 a, h7 a, h8 a { 100 | text-decoration:none; 101 | color:black; 102 | } 103 | a { 104 | } 105 | .description { 106 | font-size: 1.2em; 107 | margin-bottom: 30px; 108 | margin-top: 30px; 109 | font-style: italic; 110 | } 111 | .download { 112 | float: right; 113 | } 114 | pre { 115 | background: #F8F8F8; /* The same as the GitHub background color since I'm using their syntax.css */ 116 | padding: 0 2em 0 2em; 117 | } 118 | hr { 119 | border: 0; 120 | width: 80%; 121 | border-bottom: 1px solid #aaa; 122 | } 123 | img { 124 | border: 0px; 125 | } 126 | .footer { 127 | text-align: center; 128 | padding-top: 30px; 129 | font-style: italic; 130 | } 131 | .downloadoption { 132 | background: #eee; 133 | padding: 10px 0 10px 0; /* Keep image and text away from top and bottom */ 134 | margin-bottom: 10px; /* Ensure there is some whitespace between options */ 135 | } 136 | .downloadoptionimage { 137 | float: left; 138 | width: 90px; 139 | margin-left: 10px; /* Offset from left border a little */ 140 | } 141 | .downloadoptiontext { 142 | padding-left: 110px; /* Offset from image */ 143 | padding-right: 10px; /* Offset from right border of the download option */ 144 | } 145 | .screenshotframe { 146 | text-align: center; /* The screenshot and it's border should be in the center of the page */ 147 | } 148 | .screenshot { 149 | border: 10px solid #F8F8F8; /* Soft grey border */ 150 | } 151 | 152 | #toc { 153 | float:right; 154 | font-size:small; 155 | margin:2em 0 1em 1em; 156 | border:thin solid #ddd; 157 | background-color:#f0f0f0; 158 | } 159 | 160 | #toc ul { 161 | list-style-type: none; 162 | padding:0 1em 0 1em; 163 | } 164 | 165 | .alert { 166 | width:50%; 167 | margin:1em; 168 | padding:1em; 169 | font-size:smaller; 170 | border:thin solid hsl(0,100%,50%); 171 | background-color:hsl(0,100%,95%); 172 | font-style:italic;" 173 | } -------------------------------------------------------------------------------- /hledger-lib/hledger-lib.cabal: -------------------------------------------------------------------------------- 1 | name: hledger-lib 2 | version: 0.21.3 3 | category: Finance 4 | synopsis: Core data types, parsers and utilities for the hledger accounting tool. 5 | description: 6 | hledger is a library and set of user tools for working 7 | with financial data (or anything that can be tracked in a 8 | double-entry accounting ledger.) It is a haskell port and 9 | friendly fork of John Wiegley's Ledger. hledger provides 10 | command-line, curses and web interfaces, and aims to be a 11 | reliable, practical tool for daily use. 12 | 13 | license: GPL 14 | license-file: LICENSE 15 | author: Simon Michael 16 | maintainer: Simon Michael 17 | homepage: http://hledger.org 18 | bug-reports: http://hledger.org/bugs 19 | stability: beta 20 | tested-with: GHC==7.2.2, GHC==7.4.2, GHC==7.6.1 21 | cabal-version: >= 1.10 22 | build-type: Simple 23 | -- data-dir: data 24 | -- data-files: 25 | -- extra-tmp-files: 26 | extra-source-files: tests/suite.hs 27 | -- README 28 | -- sample.ledger 29 | -- sample.timelog 30 | 31 | library 32 | -- should set patchlevel here as in Makefile 33 | cpp-options: -DPATCHLEVEL=0 34 | exposed-modules: 35 | Hledger 36 | Hledger.Data 37 | Hledger.Data.Account 38 | Hledger.Data.AccountName 39 | Hledger.Data.Amount 40 | Hledger.Data.Commodity 41 | Hledger.Data.Dates 42 | Hledger.Data.FormatStrings 43 | Hledger.Data.Journal 44 | Hledger.Data.Ledger 45 | Hledger.Data.Posting 46 | Hledger.Data.TimeLog 47 | Hledger.Data.Transaction 48 | Hledger.Data.Types 49 | Hledger.Query 50 | Hledger.Read 51 | Hledger.Read.CsvReader 52 | Hledger.Read.JournalReader 53 | Hledger.Read.TimelogReader 54 | Hledger.Reports 55 | Hledger.Utils 56 | Hledger.Utils.UTF8IOCompat 57 | Build-Depends: 58 | base >= 4.3 && < 5 59 | ,bytestring 60 | ,cmdargs >= 0.10 && < 0.11 61 | ,containers 62 | ,csv 63 | ,directory 64 | ,filepath 65 | ,mtl 66 | ,old-locale 67 | ,old-time 68 | ,parsec 69 | ,pretty-show 70 | ,regex-compat-tdfa == 0.95.* 71 | ,regexpr >= 0.5.1 72 | ,safe >= 0.2 73 | ,split >= 0.1 && < 0.3 74 | ,time 75 | ,transformers >= 0.2 && < 0.4 76 | ,utf8-string >= 0.3.5 && < 0.4 77 | ,HUnit 78 | default-language: Haskell2010 79 | 80 | source-repository head 81 | type: git 82 | location: https://github.com/simonmichael/hledger 83 | 84 | test-suite tests 85 | type: exitcode-stdio-1.0 86 | main-is: tests/suite.hs 87 | ghc-options: -Wall 88 | build-depends: hledger-lib 89 | , base >= 4.3 && < 5 90 | , cmdargs 91 | , containers 92 | , csv 93 | , directory 94 | , filepath 95 | , HUnit 96 | , mtl 97 | , old-locale 98 | , old-time 99 | , parsec 100 | , pretty-show 101 | , regex-compat-tdfa 102 | , regexpr 103 | , safe 104 | , split 105 | , test-framework 106 | , test-framework-hunit 107 | , time 108 | , transformers 109 | default-language: Haskell2010 110 | 111 | -- cf http://www.haskell.org/cabal/release/cabal-latest/doc/users-guide/authors.html 112 | 113 | -- Additional dependencies: 114 | -- required for make test: test-framework, test-framework-hunit 115 | -- required for make bench: tabular-0.1 116 | -------------------------------------------------------------------------------- /hledger/Hledger/Cli/Stats.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | 3 | Print some statistics for the journal. 4 | 5 | -} 6 | 7 | module Hledger.Cli.Stats 8 | where 9 | import Data.List 10 | import Data.Maybe 11 | import Data.Ord 12 | import Data.Time.Calendar 13 | import Text.Printf 14 | import qualified Data.Map as Map 15 | 16 | import Hledger 17 | import Hledger.Cli.Options 18 | import Prelude hiding (putStr) 19 | import Hledger.Utils.UTF8IOCompat (putStr) 20 | 21 | 22 | -- like Register.summarisePostings 23 | -- | Print various statistics for the journal. 24 | stats :: CliOpts -> Journal -> IO () 25 | stats CliOpts{reportopts_=reportopts_} j = do 26 | d <- getCurrentDay 27 | let q = queryFromOpts d reportopts_ 28 | l = ledgerFromJournal q j 29 | reportspan = (ledgerDateSpan l) `orDatesFrom` (queryDateSpan False q) 30 | intervalspans = splitSpan (intervalFromOpts reportopts_) reportspan 31 | showstats = showLedgerStats l d 32 | s = intercalate "\n" $ map showstats intervalspans 33 | putStr s 34 | 35 | showLedgerStats :: Ledger -> Day -> DateSpan -> String 36 | showLedgerStats l today span = 37 | unlines $ map (\(label,value) -> concatBottomPadded [printf fmt1 label, value]) stats 38 | where 39 | fmt1 = "%-" ++ show w1 ++ "s: " 40 | -- fmt2 = "%-" ++ show w2 ++ "s" 41 | w1 = maximum $ map (length . fst) stats 42 | -- w2 = maximum $ map (length . show . snd) stats 43 | stats = [ 44 | ("Main journal file" :: String, path) -- ++ " (from " ++ source ++ ")") 45 | ,("Included journal files", unlines $ reverse $ -- cf journalAddFile 46 | drop 1 $ journalFilePaths j) 47 | ,("Transactions span", printf "%s to %s (%d days)" (start span) (end span) days) 48 | ,("Last transaction", maybe "none" show lastdate ++ showelapsed lastelapsed) 49 | ,("Transactions", printf "%d (%0.1f per day)" tnum txnrate) 50 | ,("Transactions last 30 days", printf "%d (%0.1f per day)" tnum30 txnrate30) 51 | ,("Transactions last 7 days", printf "%d (%0.1f per day)" tnum7 txnrate7) 52 | ,("Payees/descriptions", show $ length $ nub $ map tdescription ts) 53 | ,("Accounts", printf "%d (depth %d)" acctnum acctdepth) 54 | ,("Commodities", printf "%s (%s)" (show $ length cs) (intercalate ", " cs)) 55 | -- Transactions this month : %(monthtxns)s (last month in the same period: %(lastmonthtxns)s) 56 | -- Uncleared transactions : %(uncleared)s 57 | -- Days since reconciliation : %(reconcileelapsed)s 58 | -- Days since last transaction : %(recentelapsed)s 59 | ] 60 | where 61 | j = ljournal l 62 | path = journalFilePath j 63 | ts = sortBy (comparing tdate) $ filter (spanContainsDate span . tdate) $ jtxns j 64 | as = nub $ map paccount $ concatMap tpostings ts 65 | cs = Map.keys $ canonicalStyles $ concatMap amounts $ map pamount $ concatMap tpostings ts 66 | lastdate | null ts = Nothing 67 | | otherwise = Just $ tdate $ last ts 68 | lastelapsed = maybe Nothing (Just . diffDays today) lastdate 69 | showelapsed Nothing = "" 70 | showelapsed (Just days) = printf " (%d %s)" days' direction 71 | where days' = abs days 72 | direction | days >= 0 = "days ago" :: String 73 | | otherwise = "days from now" 74 | tnum = length ts 75 | start (DateSpan (Just d) _) = show d 76 | start _ = "" 77 | end (DateSpan _ (Just d)) = show d 78 | end _ = "" 79 | days = fromMaybe 0 $ daysInSpan span 80 | txnrate | days==0 = 0 81 | | otherwise = fromIntegral tnum / fromIntegral days :: Double 82 | tnum30 = length $ filter withinlast30 ts 83 | withinlast30 t = d >= addDays (-30) today && (d<=today) where d = tdate t 84 | txnrate30 = fromIntegral tnum30 / 30 :: Double 85 | tnum7 = length $ filter withinlast7 ts 86 | withinlast7 t = d >= addDays (-7) today && (d<=today) where d = tdate t 87 | txnrate7 = fromIntegral tnum7 / 7 :: Double 88 | acctnum = length as 89 | acctdepth | null as = 0 90 | | otherwise = maximum $ map accountNameLevel as 91 | 92 | -------------------------------------------------------------------------------- /hledger-lib/Hledger/Utils/UTF8IOCompat.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {- | 3 | 4 | UTF-8 aware string IO functions that will work across multiple platforms 5 | and GHC versions. Includes code from Text.Pandoc.UTF8 ((C) 2010 John 6 | MacFarlane). 7 | 8 | Example usage: 9 | 10 | import Prelude hiding (readFile,writeFile,appendFile,getContents,putStr,putStrLn) 11 | import UTF8IOCompat (readFile,writeFile,appendFile,getContents,putStr,putStrLn) 12 | import UTF8IOCompat (SystemString,fromSystemString,toSystemString,error',userError') 13 | 14 | 2013/4/10 update: we now trust that current GHC versions & platforms 15 | do the right thing, so this file is a no-op and on its way to being removed. 16 | Not carefully tested. 17 | 18 | -} 19 | 20 | module Hledger.Utils.UTF8IOCompat ( 21 | readFile, 22 | writeFile, 23 | appendFile, 24 | getContents, 25 | hGetContents, 26 | putStr, 27 | putStrLn, 28 | hPutStr, 29 | hPutStrLn, 30 | -- 31 | SystemString, 32 | fromSystemString, 33 | toSystemString, 34 | error', 35 | userError', 36 | ) 37 | where 38 | 39 | -- import Control.Monad (liftM) 40 | -- import qualified Data.ByteString.Lazy as B 41 | -- import qualified Data.ByteString.Lazy.Char8 as B8 42 | -- import qualified Data.ByteString.Lazy.UTF8 as U8 (toString, fromString) 43 | import Prelude hiding (readFile, writeFile, appendFile, getContents, putStr, putStrLn) 44 | import System.IO -- (Handle) 45 | -- #if __GLASGOW_HASKELL__ < 702 46 | -- import Codec.Binary.UTF8.String as UTF8 (decodeString, encodeString, isUTF8Encoded) 47 | -- import System.Info (os) 48 | -- #endif 49 | 50 | -- bom :: B.ByteString 51 | -- bom = B.pack [0xEF, 0xBB, 0xBF] 52 | 53 | -- stripBOM :: B.ByteString -> B.ByteString 54 | -- stripBOM s | bom `B.isPrefixOf` s = B.drop 3 s 55 | -- stripBOM s = s 56 | 57 | -- readFile :: FilePath -> IO String 58 | -- readFile = liftM (U8.toString . stripBOM) . B.readFile 59 | 60 | -- writeFile :: FilePath -> String -> IO () 61 | -- writeFile f = B.writeFile f . U8.fromString 62 | 63 | -- appendFile :: FilePath -> String -> IO () 64 | -- appendFile f = B.appendFile f . U8.fromString 65 | 66 | -- getContents :: IO String 67 | -- getContents = liftM (U8.toString . stripBOM) B.getContents 68 | 69 | -- hGetContents :: Handle -> IO String 70 | -- hGetContents h = liftM (U8.toString . stripBOM) (B.hGetContents h) 71 | 72 | -- putStr :: String -> IO () 73 | -- putStr = bs_putStr . U8.fromString 74 | 75 | -- putStrLn :: String -> IO () 76 | -- putStrLn = bs_putStrLn . U8.fromString 77 | 78 | -- hPutStr :: Handle -> String -> IO () 79 | -- hPutStr h = bs_hPutStr h . U8.fromString 80 | 81 | -- hPutStrLn :: Handle -> String -> IO () 82 | -- hPutStrLn h = bs_hPutStrLn h . U8.fromString 83 | 84 | -- -- span GHC versions including 6.12.3 - 7.4.1: 85 | -- bs_putStr = B8.putStr 86 | -- bs_putStrLn = B8.putStrLn 87 | -- bs_hPutStr = B8.hPut 88 | -- bs_hPutStrLn h bs = B8.hPut h bs >> B8.hPut h (B.singleton 0x0a) 89 | 90 | 91 | -- | A string received from or being passed to the operating system, such 92 | -- as a file path, command-line argument, or environment variable name or 93 | -- value. With GHC versions before 7.2 on some platforms (posix) these are 94 | -- typically encoded. When converting, we assume the encoding is UTF-8 (cf 95 | -- ). 96 | type SystemString = String 97 | 98 | -- | Convert a system string to an ordinary string, decoding from UTF-8 if 99 | -- it appears to be UTF8-encoded and GHC version is less than 7.2. 100 | fromSystemString :: SystemString -> String 101 | -- #if __GLASGOW_HASKELL__ < 702 102 | -- fromSystemString s = if UTF8.isUTF8Encoded s then UTF8.decodeString s else s 103 | -- #else 104 | fromSystemString = id 105 | -- #endif 106 | 107 | -- | Convert a unicode string to a system string, encoding with UTF-8 if 108 | -- we are on a posix platform with GHC < 7.2. 109 | toSystemString :: String -> SystemString 110 | -- #if __GLASGOW_HASKELL__ < 702 111 | -- toSystemString = case os of 112 | -- "unix" -> UTF8.encodeString 113 | -- "linux" -> UTF8.encodeString 114 | -- "darwin" -> UTF8.encodeString 115 | -- _ -> id 116 | -- #else 117 | toSystemString = id 118 | -- #endif 119 | 120 | -- | A SystemString-aware version of error. 121 | error' :: String -> a 122 | error' = error . toSystemString 123 | 124 | -- | A SystemString-aware version of userError. 125 | userError' :: String -> IOError 126 | userError' = userError . toSystemString 127 | -------------------------------------------------------------------------------- /hledger-web/Handler/Utils.hs: -------------------------------------------------------------------------------- 1 | -- | Web handler utilities. 2 | 3 | module Handler.Utils where 4 | 5 | import Prelude 6 | import Control.Applicative ((<$>)) 7 | import Data.IORef 8 | import Data.Maybe 9 | import Data.Text(pack,unpack) 10 | import Data.Time.Calendar 11 | import Data.Time.Clock 12 | import Data.Time.Format 13 | import System.Locale (defaultTimeLocale) 14 | import Text.Hamlet 15 | import Yesod.Core 16 | 17 | import Foundation 18 | 19 | import Hledger hiding (is) 20 | import Hledger.Cli hiding (version) 21 | import Hledger.Web.Options 22 | 23 | 24 | -- | A bundle of data useful for hledger-web request handlers and templates. 25 | data ViewData = VD { 26 | opts :: WebOpts -- ^ the command-line options at startup 27 | ,here :: AppRoute -- ^ the current route 28 | ,msg :: Maybe Html -- ^ the current UI message if any, possibly from the current request 29 | ,today :: Day -- ^ today's date (for queries containing relative dates) 30 | ,j :: Journal -- ^ the up-to-date parsed unfiltered journal 31 | ,q :: String -- ^ the current q parameter, the main query expression 32 | ,m :: Query -- ^ a query parsed from the q parameter 33 | ,qopts :: [QueryOpt] -- ^ query options parsed from the q parameter 34 | ,am :: Query -- ^ a query parsed from the accounts sidebar query expr ("a" parameter) 35 | ,aopts :: [QueryOpt] -- ^ query options parsed from the accounts sidebar query expr 36 | ,showpostings :: Bool -- ^ current p parameter, 1 or 0 shows/hides all postings where applicable 37 | } 38 | 39 | -- | Make a default ViewData, using day 0 as today's date. 40 | nullviewdata :: ViewData 41 | nullviewdata = viewdataWithDateAndParams nulldate "" "" "" 42 | 43 | -- | Make a ViewData using the given date and request parameters, and defaults elsewhere. 44 | viewdataWithDateAndParams :: Day -> String -> String -> String -> ViewData 45 | viewdataWithDateAndParams d q a p = 46 | let (querymatcher,queryopts) = parseQuery d q 47 | (acctsmatcher,acctsopts) = parseQuery d a 48 | in VD { 49 | opts = defwebopts 50 | ,j = nulljournal 51 | ,here = RootR 52 | ,msg = Nothing 53 | ,today = d 54 | ,q = q 55 | ,m = querymatcher 56 | ,qopts = queryopts 57 | ,am = acctsmatcher 58 | ,aopts = acctsopts 59 | ,showpostings = p == "1" 60 | } 61 | 62 | -- | Gather data used by handlers and templates in the current request. 63 | getViewData :: Handler ViewData 64 | getViewData = do 65 | app <- getYesod 66 | let opts@WebOpts{cliopts_=copts@CliOpts{reportopts_=ropts}} = appOpts app 67 | (j, err) <- getCurrentJournal app copts{reportopts_=ropts{no_elide_=True}} 68 | msg <- getMessageOr err 69 | Just here <- getCurrentRoute 70 | today <- liftIO getCurrentDay 71 | q <- getParameterOrNull "q" 72 | a <- getParameterOrNull "a" 73 | p <- getParameterOrNull "p" 74 | return (viewdataWithDateAndParams today q a p){ 75 | opts=opts 76 | ,msg=msg 77 | ,here=here 78 | ,today=today 79 | ,j=j 80 | } 81 | where 82 | -- | Update our copy of the journal if the file changed. If there is an 83 | -- error while reloading, keep the old one and return the error, and set a 84 | -- ui message. 85 | getCurrentJournal :: App -> CliOpts -> Handler (Journal, Maybe String) 86 | getCurrentJournal app opts = do 87 | -- XXX put this inside atomicModifyIORef' for thread safety 88 | j <- liftIO $ readIORef $ appJournal app 89 | (jE, changed) <- liftIO $ journalReloadIfChanged opts j 90 | if not changed 91 | then return (j,Nothing) 92 | else case jE of 93 | Right j' -> do liftIO $ writeIORef (appJournal app) j' 94 | return (j',Nothing) 95 | Left e -> do setMessage $ "error while reading" {- ++ ": " ++ e-} 96 | return (j, Just e) 97 | 98 | -- | Get the named request parameter, or the empty string if not present. 99 | getParameterOrNull :: String -> Handler String 100 | getParameterOrNull p = unpack `fmap` fromMaybe "" <$> lookupGetParam (pack p) 101 | 102 | -- | Get the message set by the last request, or the newer message provided, if any. 103 | getMessageOr :: Maybe String -> Handler (Maybe Html) 104 | getMessageOr mnewmsg = do 105 | oldmsg <- getMessage 106 | return $ maybe oldmsg (Just . toHtml) mnewmsg 107 | 108 | numbered :: [a] -> [(Int,a)] 109 | numbered = zip [1..] 110 | 111 | dayToJsTimestamp :: Day -> Integer 112 | dayToJsTimestamp d = read (formatTime defaultTimeLocale "%s" t) * 1000 -- XXX read 113 | where t = UTCTime d (secondsToDiffTime 0) 114 | 115 | chomp :: String -> String 116 | chomp = reverse . dropWhile (`elem` "\r\n") . reverse 117 | 118 | -------------------------------------------------------------------------------- /hledger-lib/Hledger/Data/FormatStrings.hs: -------------------------------------------------------------------------------- 1 | module Hledger.Data.FormatStrings ( 2 | parseFormatString 3 | , formatStrings 4 | , formatValue 5 | , FormatString(..) 6 | , HledgerFormatField(..) 7 | , tests 8 | ) where 9 | 10 | import Numeric 11 | import Data.Char (isPrint) 12 | import Data.Maybe 13 | import Test.HUnit 14 | import Text.ParserCombinators.Parsec 15 | import Text.Printf 16 | 17 | import Hledger.Data.Types 18 | 19 | 20 | formatValue :: Bool -> Maybe Int -> Maybe Int -> String -> String 21 | formatValue leftJustified min max value = printf formatS value 22 | where 23 | l = if leftJustified then "-" else "" 24 | min' = maybe "" show min 25 | max' = maybe "" (\i -> "." ++ (show i)) max 26 | formatS = "%" ++ l ++ min' ++ max' ++ "s" 27 | 28 | parseFormatString :: String -> Either String [FormatString] 29 | parseFormatString input = case (runParser formatStrings () "(unknown)") input of 30 | Left y -> Left $ show y 31 | Right x -> Right x 32 | 33 | {- 34 | Parsers 35 | -} 36 | 37 | field :: GenParser Char st HledgerFormatField 38 | field = do 39 | try (string "account" >> return AccountField) 40 | <|> try (string "depth_spacer" >> return DepthSpacerField) 41 | <|> try (string "date" >> return DescriptionField) 42 | <|> try (string "description" >> return DescriptionField) 43 | <|> try (string "total" >> return TotalField) 44 | <|> try (many1 digit >>= (\s -> return $ FieldNo $ read s)) 45 | 46 | formatField :: GenParser Char st FormatString 47 | formatField = do 48 | char '%' 49 | leftJustified <- optionMaybe (char '-') 50 | minWidth <- optionMaybe (many1 $ digit) 51 | maxWidth <- optionMaybe (do char '.'; many1 $ digit) -- TODO: Can this be (char '1') *> (many1 digit) 52 | char '(' 53 | f <- field 54 | char ')' 55 | return $ FormatField (isJust leftJustified) (parseDec minWidth) (parseDec maxWidth) f 56 | where 57 | parseDec s = case s of 58 | Just text -> Just m where ((m,_):_) = readDec text 59 | _ -> Nothing 60 | 61 | formatLiteral :: GenParser Char st FormatString 62 | formatLiteral = do 63 | s <- many1 c 64 | return $ FormatLiteral s 65 | where 66 | isPrintableButNotPercentage x = isPrint x && (not $ x == '%') 67 | c = (satisfy isPrintableButNotPercentage "printable character") 68 | <|> try (string "%%" >> return '%') 69 | 70 | formatString :: GenParser Char st FormatString 71 | formatString = 72 | formatField 73 | <|> formatLiteral 74 | 75 | formatStrings :: GenParser Char st [FormatString] 76 | formatStrings = many formatString 77 | 78 | testFormat :: FormatString -> String -> String -> Assertion 79 | testFormat fs value expected = assertEqual name expected actual 80 | where 81 | (name, actual) = case fs of 82 | FormatLiteral l -> ("literal", formatValue False Nothing Nothing l) 83 | FormatField leftJustify min max _ -> ("field", formatValue leftJustify min max value) 84 | 85 | testParser :: String -> [FormatString] -> Assertion 86 | testParser s expected = case (parseFormatString s) of 87 | Left error -> assertFailure $ show error 88 | Right actual -> assertEqual ("Input: " ++ s) expected actual 89 | 90 | tests = test [ formattingTests ++ parserTests ] 91 | 92 | formattingTests = [ 93 | testFormat (FormatLiteral " ") "" " " 94 | , testFormat (FormatField False Nothing Nothing DescriptionField) "description" "description" 95 | , testFormat (FormatField False (Just 20) Nothing DescriptionField) "description" " description" 96 | , testFormat (FormatField False Nothing (Just 20) DescriptionField) "description" "description" 97 | , testFormat (FormatField True Nothing (Just 20) DescriptionField) "description" "description" 98 | , testFormat (FormatField True (Just 20) Nothing DescriptionField) "description" "description " 99 | , testFormat (FormatField True (Just 20) (Just 20) DescriptionField) "description" "description " 100 | , testFormat (FormatField True Nothing (Just 3) DescriptionField) "description" "des" 101 | ] 102 | 103 | parserTests = [ 104 | testParser "" [] 105 | , testParser "D" [FormatLiteral "D"] 106 | , testParser "%(date)" [FormatField False Nothing Nothing DescriptionField] 107 | , testParser "%(total)" [FormatField False Nothing Nothing TotalField] 108 | , testParser "Hello %(date)!" [FormatLiteral "Hello ", FormatField False Nothing Nothing DescriptionField, FormatLiteral "!"] 109 | , testParser "%-(date)" [FormatField True Nothing Nothing DescriptionField] 110 | , testParser "%20(date)" [FormatField False (Just 20) Nothing DescriptionField] 111 | , testParser "%.10(date)" [FormatField False Nothing (Just 10) DescriptionField] 112 | , testParser "%20.10(date)" [FormatField False (Just 20) (Just 10) DescriptionField] 113 | , testParser "%20(account) %.10(total)\n" [ FormatField False (Just 20) Nothing AccountField 114 | , FormatLiteral " " 115 | , FormatField False Nothing (Just 10) TotalField 116 | , FormatLiteral "\n" 117 | ] 118 | ] 119 | -------------------------------------------------------------------------------- /extra/aliases.sh: -------------------------------------------------------------------------------- 1 | # some hledger-related bash aliases 2 | 3 | # time 4 | 5 | # all in one big journal: 6 | #export TIMELOG=~/personal/time.journal 7 | 8 | alias hours="hledger -f $TIMELOG" 9 | alias today='hours -p today' 10 | alias yesterday='hours -p yesterday' 11 | alias thisweek='hours -p thisweek' 12 | alias lastweek='hours -p lastweek' 13 | alias thismonth='hours -p thismonth' 14 | alias lastmonth='hours -p lastmonth' 15 | alias thisyear='hours -p thisyear' 16 | alias lastyear='hours -p lastyear' 17 | alias janhours="hours -p jan" 18 | alias febhours="hours -p feb" 19 | alias marhours="hours -p mar" 20 | alias aprhours="hours -p apr" 21 | alias mayhours="hours -p may" 22 | alias junhours="hours -p jun" 23 | alias julhours="hours -p jul" 24 | alias aughours="hours -p aug" 25 | alias sephours="hours -p sep" 26 | alias octhours="hours -p oct" 27 | alias novhours="hours -p nov" 28 | alias dechours="hours -p dec" 29 | alias 2008hours="hours -p 2008" 30 | alias 2009hours="hours -p 2009" 31 | alias 2010hours="hours -p 2010" 32 | alias 2011hours="hours -p 2011" 33 | alias 2012hours="hours -p 2012" 34 | alias 2013hours="hours -p 2013" 35 | alias 2014hours="hours -p 2014" 36 | alias 2015hours="hours -p 2015" 37 | alias weeklyhours="hours -p 'weekly this year' register --empty" 38 | alias monthlyhours="hours -p 'monthly this year' register --empty" 39 | alias weeklybillablehours="weeklyhours jobs not:unbilled --depth 3" 40 | alias monthlybillablehours="monthlyhours jobs not:unbilled --depth 3" 41 | 42 | # money 43 | 44 | # one journal per year, included by current and all journals: 45 | #export LEDGER_FILE=~/personal/current.journal 46 | #export LEDGER_FILE=~/personal/all.journal 47 | 48 | alias jan="hledger -p jan" 49 | alias feb="hledger -p feb" 50 | alias mar="hledger -p mar" 51 | alias apr="hledger -p apr" 52 | alias may="hledger -p may" 53 | alias jun="hledger -p jun" 54 | alias jul="hledger -p jul" 55 | alias aug="hledger -p aug" 56 | alias sep="hledger -p sep" 57 | alias oct="hledger -p oct" 58 | alias nov="hledger -p nov" 59 | alias dec="hledger -p dec" 60 | alias 2006='hledger -f ~/personal/2006.journal' 61 | alias 2007='hledger -f ~/personal/2007.journal' 62 | alias 2008='hledger -f ~/personal/2008.journal' 63 | alias 2009='hledger -f ~/personal/2009.journal' 64 | alias 2010='hledger -f ~/personal/2010.journal' 65 | alias 2011='hledger -f ~/personal/2011.journal' 66 | alias 2012='hledger -f ~/personal/2012.journal' 67 | alias 2013='hledger -f ~/personal/2013.journal' 68 | alias 2014='hledger -f ~/personal/2014.journal' 69 | alias 2015='hledger -f ~/personal/2015.journal' 70 | alias all='hledger -f ~/personal/all.journal' 71 | alias household='hledger -f ~/personal/household.journal' 72 | alias add='hledger add' 73 | 74 | alias bankbalances='hledger bal assets:bank liabilities:credit -E' 75 | alias cashflow="hledger balance '^assets:(bank|cash)'" 76 | alias incexp="hledger balance '(^income|^expenses|^equity:draw)'" 77 | # show activity in these accounts this and last month 78 | alias cash="hledger -d 'd>=[last month]' reg 'assets:cash' -B" 79 | alias checking="hledger -d 'd>=[last month]' reg 'assets:bank:wells fargo:checking' -B" 80 | # show daily cleared checking balance - for reconciling with online bank statement 81 | alias checkingcleared="checking --cleared --period 'daily to tomorrow'" 82 | # show checking balance from today forward 83 | alias checkingfuture="checking -d 'd>=[yesterday]'" 84 | 85 | # generate a chart and view it in emacs 86 | function chart () { 87 | hledger chart $* && emacsclient -n hledger.png 88 | } 89 | 90 | # old ledger 2.6 scripts 91 | 92 | function BalanceSheet() { 93 | echo "Balance sheet as of `date`" 94 | echo "totals include sub-accounts" 95 | echo 96 | ledger -n --balance-format '%10T %2_%-a\n' --display "l<=3" --basis --subtotal $* balance assets 97 | echo 98 | ledger -n --balance-format '%10T %2_%-a\n' --display "l<=3" --basis --subtotal $* balance liabilities 99 | echo 100 | ledger -nE --balance-format '%10T %2_%-a\n' --display "l<=4" --basis --subtotal $* balance equity 101 | echo 102 | echo "`ledger --balance-format '%10T %2_%-a\n' --basis $* balance liabilities equity | tail -1`liabilities + equity" 103 | echo 104 | ledger --balance-format '%10T %2_%-a\n' --basis $* balance assets liabilities | tail -2 105 | } 106 | 107 | function IncomeStatement() { 108 | echo "Income statement for `date +%Y` as of `date`" 109 | echo "totals include sub-accounts" 110 | echo 111 | ledger -n --balance-format '%10(-T) %2_%-a\n' --display "l<=3" --basis --subtotal $* balance income 112 | echo 113 | ledger -n --balance-format '%10(-T) %2_%-a\n' --display "l<=2" --basis --subtotal $* balance expenses -equity 114 | echo 115 | ledger --balance-format '%10(-T) %2_%-a\n' --basis $* balance income expenses -equity | tail -2 116 | } 117 | 118 | # function CashflowStatement () { 119 | # echo "Cashflow statement for `date +%Y`" 120 | # #echo "(totals include sub-accounts)" 121 | # echo 122 | # cat <