hledger/doc/notes.org
Simon Michael db1d866f07 notes
2014-10-28 18:40:21 -07:00

130 KiB
Raw Blame History

hledger project notes

We are all wandering the orderly halls of Castle Haskell.

This whole plain is gunpowder a mile deep.

Abstractions: precise, elegant, reusable. Implementations: correct, efficient, maintainable. Documentation: clear, simple, accurate.

A concept is tolerated inside the microkernel only if moving it outside the kernel, i.e. permitting competing implementations, would prevent the implementation of the systems required functionality.

about

This emacs org-mode outline contains Simon's project backlog, activity log, and random developer notes. Currently only I use this file, but anyone can send a patch.

backlog

Todos and wishlist items, roughly grouped by category and ordered by (my) priority. Used for planning, and often as a more lightweight bug tracker. Also contains..

estimates

I keep effort-to-complete estimates on backlog tasks as N[dwm] at the end of the line following two spaces, meaning N hours, days, weeks or months, assuming 4 hours in day, 5 days in a week, and 4 weeks in a month.

burndown charts

At some point there might be a process to aggregate estimates, log them (including deleted items as 0) on commit, and make burndown charts.

routines

standard procedures/checklists for various activities

misc

inspiration

"…simplicity of design was the most essential, guiding principle. Clarity of concepts, economy of features, efficiency and reliability of implementations were its consequences." Niklaus Wirth

"The competent programmer is fully aware of the limited size of his own skull. He therefore approaches his task with full humility, and avoids clever tricks like the plague." Edsger Dijkstra

"I was hesitating to cross the street in Edinburgh one day, and these two little old Scottish ladies cried out to me 'LIVE DANGEROUSLY, SON! LIVE DANGEROUSLY'" kowey

ALL THAT'S NEEDED IS THE DESIRE TO BE HEARD. THE WILL TO LEARN. AND THE ABILITY TO SEE. Scott McCloud, Understanding Comics

"I kept account of every farthing I spent…" Gandhi, Autobiography http://books.google.com/books?id=OXoRs7Kxs_YC&lpg=PA47&ots=Q-JUe-6Rq5&dq=%22I%20kept%20account%20of%20every%20farthing%20I%20spent%22&pg=PA47#v=onepage&q=%22I%20kept%20account%20of%20every%20farthing%20I%20spent%22&f=false

principles

we aim to make reliable, maintainable, usable, useful software, sustainably.

docs before packaging before tests before fixes before refactoring before features

"bugs" are errors, as in the programmers messed up

automate

measure

test continuously, test everything

less is more

code review/pair programming

things I want to know about money and time

time

where have I been spending my time in recent weeks ? where have I spent my time today ? what is my status wrt spending plan for this week/month/year ? what is my current status wrt time spending goals ?

money

where have I been spending my money ? what is my status wrt spending plan for this week/month/year ? what is my current status wrt spending/savings goals ? what are all my current balances ? what does my balance history look like ? what does my balance future look like ? are there any cashflow, tax, budgetary problems looming ?

charts

[1:27pm] <sm> I have decided I am not getting enough visible day-to-day value out of my ledger, I need more of that to stay motivated [1:27pm] <Nafai> What do you think will help in that? [1:27pm] <sm> I think some simple self-updating charts, or even good reports in a visible place [1:28pm] <sm> something I don't have to spend an hour fiddling with to get answers [1:38pm] <sm> Nafai: identifying/designing some useful reports/charts seems to be blocking me [1:39pm] <sm> there are probably some standard ones I should use [1:40pm] <sm> a graph of daily net worth is probably one of the simplest [1:58pm] <sm> what else.. a chart of weekly expenses in key categories [1:58pm] <sm> ditto, monthly [1:58pm] <sm> a chart of monthly income [1:59pm] <sm> those three should help me be more clear about cashflow status [2:00pm] <sm> also I'd like something that shows me how much I am on top of financial tracking - how current my numbers are, when last reconciled etc - at a glance [2:01pm] <sm> another simple one: current balances in all accounts [2:01pm] <sm> those would be a great start [2:04pm] <sm> daily net worth, weekly expense, monthly expense, monthly income, confidence/currentness report, and balance report [2:05pm] <sm> let's see, which of those 6 would give most payoff right now [2:05pm] <sm> probably 5 [2:06pm] <sm> how could I measure that ? [2:06pm] <sm> number of days since last ledger entry.. [2:06pm] <sm> number of ledger entries in last 30 days (compared to average) [2:07pm] <sm> number of days since last cleared checking entry (indicating an online reconcile) [2:08pm] <sm> those would be a good start. How do I make those visual [2:09pm] <sm> well I guess the first step is a script to print them

other docs

hledger ghci examples

This is the main object you'll deal with as a user of the Ledger library.

The most useful functions also have shorter, lower-case aliases for easier interaction. Here's an example:

> > import Hledger.Data > > j <- readJournal "sample.ledger" > > let l = journalToLedger nullfilterspec j > > accountnames l > ["assets","assets:bank","assets🏦checking","assets🏦saving",… > > accounts l > [Account assets with 0 txns and $-1 balance,Account assets:bank with... > > topaccounts l > [Account assets with 0 txns and $-1 balance,Account expenses with… > > account l "assets" > Account assets with 0 txns and $-1 balance > > accountsmatching ["ch"] l > accountsmatching ["ch"] l > [Account assets🏦checking with 4 txns and $0 balance] > > subaccounts l (account l "assets") > subaccounts l (account l "assets") > [Account assets:bank with 0 txns and $1 balance,Account assets:cash... > > head $ transactions l > 2008/01/01 income assets🏦checking $1 RegularPosting > > accounttree 2 l > Node {rootLabel = Account top with 0 txns and 0 balance, subForest = [... > > accounttreeat l (account l "assets") > Just (Node {rootLabel = Account assets with 0 txns and $-1 balance, … > > datespan l disabled > DateSpan (Just 2008-01-01) (Just 2009-01-01) > > rawdatespan l > DateSpan (Just 2008-01-01) (Just 2009-01-01) > > ledgeramounts l > [$1,$-1,$1,$-1,$1,$-1,$1,$1,$-2,$1,$-1] > > commodities l > [Commodity {symbol = "$", side = L, spaced = False, comma = False, …

ledger budgeting/forecasting

seanh:

With `budget` you can compare your budgeted transactions to your actual transactions and see whether you are under or over your budget.

The way it works is this: say you have a budget entry that moves £50 from Assets into Expenses:Cash every week:

~ Weekly Expenses:Cash £50 Assets

When you run register or balance with `budget` ledger will insert reverse transactions that move £50 from Expenses:Cash into Assets every week. These are called budget entries. The idea is that your real transactions that move money from Assets into Expenses will offset the inserted budget entries that move money the other way. The budget entries and the real transactions should sum to zero, if they don't then it shows how much you have overspent or underspent.

For example:

ledger budget balance '^expenses'

balances your budgeted expenses against your actual expenses on those budgeted accounts (sub-accounts of expenses that do not appear in the budget are ignored in this calculation). The sum of the budget entries (which move money out of expenses accounts) and your real transactions (which move money into expenses accounts) should be 0. If the sum is positive then it shows how much you've overspent, if it's negative then it shows how much you've underspent.

You can do the same with register and get a print out of each transaction (budget entries and real transactions) with a running total:

ledger budget register '^expenses'

And you can produce weekly, monthly or yearly budget reports:

ledger budget weekly register '^expenses' ledger budget monthly register '^expenses' ledger budget yearly register '^expenses'

These will only output reports for each week, month or year that has passed (your ledger file contains transactions dated later than that week, month, or year). You can see how well you did last week (or month, or year) but you can't see how well you're doing so far this week (month, year).

The `unbudgeted` argument will show (and sum) all your expenses for accounts that are not budgeted, and the `add-budget` argument will consider all your expenses budgeted or not with the budget entries added in.

With `forecast` you can project your budget into the future to see, for example, when some account will reach 0. For example, to predict your net worth:

ledger forecast 'd<[2012]' register '^assets' '^liabilities'

Or to see how your expenses will add up:

ledger forecast 'd<[2012]' register '^expenses'

essential/getting started info

I've never used financial management software before, I'm just confused at what I'm doing. http://en.wikipedia.org/wiki/Double-entry_bookkeeping_system money isn't created or destroyed, it moves between accounts all possible accounts are organised under five categories: assets, liabilities, equity, income, expenses

the gist of it for *ledger users is that each transaction in your journal (file) is balanced, ie its postings add up to zero typically you have a posting to some account (expenses:food $10) and an equal posting from another (assets:cash $-10)

so should things like income be a forever-decreasing value? yes

I think traditional bookkeeping uses "debit" and "credit" for (among other things) hiding the negative sign I wonder, if folks had been comfortable with negative numbers in the middle ages, if debit/credit would have been invented

so, when I start a ledger file and I start my initial account balances for, say, checking, I withdraw them from equity or income? Or does it matter in this case? traditionally, you transfer opening balances from equity and this is just a convention, or is there some better reason behind it? I believe it's actually based on the real-world meaning, and makes sense if you study enough bookkeeping I thought equity was more a share of something owned. that's right, and if you squint enough the two uses are equivalent So I'd do something like "assets:checking $foo \ assets:savings $bar \ liabilities:creditcard $-baz \ equity:opening balance"? yes

what about loans? Those are liabilities, right? yes

okay. So after I set up my initial account balances, it's just a matter of keeping track how and what I spend. yup, tracking your checking account's or your wallet's inflows and outflows is a good way to start Gradually you'll add more tricky things like invoices and short-term loans (accounts receivable/payable)

I also read in the manual that you can set up routine actions, like debiting from one account and crediting to another on a monthly basis. this can help me set up budgets, right? yes, ledger lets you specify those with special modifier/periodic transactions. They appear in reports but not in your journal file. Or you can use cron or something to actually add them to the journal and there's also a budget report feature

a catalog of standard bookkeeping entries for typical real-world transactions is really helpful and worth searching for

selinger article on currency & capital gains accounting

parsec lexing/expression parsing tutorial: http://burz.github.io/2014/06/24/parsing.html

hledger feedback

fabrice niessen

+For me, what would be very useful for a 1.0 version would be:

+- @check directive (see beancount), but implemented as a comment for ledger,

  • so that ledger does not get confused by this, and that you can implement
  • more features without breaking backward compatibility;

+- account declaration (see beancount), in ledger comments. Giving an account

  • number would (or could) help for the reporting stuff, for knowing which
  • value to get to read, for inserting in a given report;

+- some built-in ratios for being able to see the health of the finances (see

  • my Excel file, if you're interested);

+- easier standard outputs, such as the one above (with expenses and income in

  • 2 columns).

+- real report generation (I thought at LaTeX as in SQL Ledger, but I am now

  • heading and producing reports through Org, which is 1000x better). Results
  • soon.

Martin Wuertele, debian:

I see our task not in keeping accounts (that's in the responsibility of the trusted bodies) but more in management accounts. In order to achieve that we need a solution that mirrors the financials of the trusted bodies, has a way to streamline them (allign different local chart of accounts or reporting formats to an unified one), do some reclassifications and accruals on top, performe currency conversions (we have debian.uk, debian.ch, FFIS, SPI-INC,…), accumulate the results, add additional reclassifications and accruals on top and, in some cases, add consolidation entries (e.g. SPI-INC does reembursement but gets itself reembursed by FFIS).

We do not bother with any local tasks like income tax, vat or statistical filing, invoicing and the like.

my hamlet feedback

thread data through nested templates with Reader monad

HDString constructor for HamletData ?

data type for non-RT Hamlet as well, or better, the same type for both

allow (RT or non-RT) templates in $ $ as well, drop ^ ^

allow literal arguments in references

easier verbatim content quoting, eg lines starting with \\. Having to escape $$ is not so convenient for jquery

docs and compiler errors should say something clearer than "Hamlet url" (Hamlet routetype, urltype, routet, urlt ?)

2010/8

$ $ could handle templates as well; drop ^ ^
@ @ could recognise tuples automatically; drop ?
why !: : for conditional attributes ? How about !? ?

snippets

type Regexp = String

regexMatchesRegexCompat :: Regexp -> String -> Bool regexMatchesRegexCompat = flip (=~)

{- | A simple accounts view. This one is json-capable, returning the chart of accounts as json if the Accept header specifies json. getAccountsR :: Handler RepHtmlJson getAccountsR = do vd@VD{..} <- getViewData let j' = filterJournalPostings2 m j html = do setTitle "hledger-web accounts" toWidget $ accountsReportAsHtml opts vd $ accountsReport2 (reportopts_ $ cliopts_ opts) am j' json = jsonMap [("accounts", toJSON $ journalAccountNames j')] defaultLayoutJson html json

| A json-only version of "getAccountsR", does not require the special Accept header. getAccountsJsonR :: Handler RepJson getAccountsJsonR = do VD{..} <- getViewData let j' = filterJournalPostings2 m j jsonToRepJson $ jsonMap [("accounts", toJSON $ journalAccountNames j')] -}

let assertAccountsReportItemEqual ((ea1,ea2,ei,eamt), (aa1,aa2,ai,aamt)) = do assertEqual "full account name" ea1 aa1 assertEqual "short account name" ea2 aa2 assertEqual "indent" ei ai assertEqual "amount" eamt aamt (showMixedAmountDebug eamt) (showMixedAmountDebug aamt)

assertEqualAccount eacct@Account{aname=eaname,apostings=eapostings,abalance=eabalance} aacct@Account{aname=aaname,apostings=aapostings,abalance=aabalance} = do assertEqual "account name" eaname aaname assertEqual "account postings" eapostings aapostings assertEqual "account balance" eabalance aabalance let (Mixed eamts, Mixed aamts) = (eabalance, aabalance) mapM_ (\(e,a) -> assertEqual "account balance amount" e a) $ zip eamts aamts assertEqual "account balance amount lists" (eamts) (aamts) assertEqual "account balance mixed amounts" (Mixed eamts) (Mixed aamts)

fromOfxTransaction :: StatementTransaction -> LedgerTransaction fromOfxTransaction StatementTransaction { stType = _ sttype :: TransactionType ,stDatePosted = stdateposted :: Maybe UTCTime ,stAmount = stamount :: Decimal ,stCheckNumber = stchecknumber :: Maybe Int ,stFITID = _ stfitid :: String ,stSIC = _ stsic :: Maybe String ,stName = stname :: String } = LedgerTransaction { ltdate = date :: Day, ,ltstatus = stat :: Bool, ,ltcode = code :: String, ,ltdescription = desc :: String, ,ltcomment = com :: String, ,ltpostings = ps :: [Posting], ,ltpreceding_comment_lines = prec :: String } where date = maybe (error "found an undated bank transaction, giving up") utctDay stdateposted stat = False code = maybe "" show stchecknumber desc = stname com = "" ps = [ Posting False "UNKNOWN" a "" RegularPosting, Posting False "CHECKING" (-a) "" RegularPosting ] prec = "" a = Mixed [dollars $ fromDecimal stamount] fromDecimal d = fromIntegral (decimalMantissa d) / (10 ^ decimalPlaces d)

Name: test Version: 0.1 Synopsis: test package for linking against internal libraries Author: Stefan Wehr Build-type: Simple Cabal-version: >=1.8 IMPORTANT

Library Hs-source-dirs: lib IMPORTANT Exposed-modules: A Build-Depends: base >= 4

Executable test-exe Build-depends: base >= 4, test, link against the internal library Main-is: Main.hs imports A Hs-source-dirs: prog IMPORTANT

trace a MixedAmount matrace :: MixedAmount -> MixedAmount matrace a@(Mixed as) = trace (show as) a

normalise and trace a MixedAmount nmatrace :: MixedAmount -> MixedAmount nmatrace a = trace (show as) a where (Mixed as) = normaliseMixedAmount a

cabal test import System.FilePath main = defaultMainWithHooks $ simpleUserHooks { runTests = runTests' } runTests' :: Args -> Bool -> PackageDescription -> LocalBuildInfo -> IO () runTests' _ _ _ lbi = system testprog >> return () where testprog = (buildDir lbi) </> "hledger" </> "hledger test"

queryStringFromAP a p = if null ap then "" else "?" ++ ap where ap = intercalate "&" [a',p'] a' = if null a then "" else printf "&a=%s" a p' = if null p then "" else printf "&p=%s" p

toggleScriptFor name = [$hamlet| -- <script type="text/javascript"> -- function $name$Toggle() { -- e = document.getElementById('$name$'); -- link = document.getElementById('$name$link'); if (e.style.display == 'none') { link.style['font-weight'] = 'bold'; e.style.display = 'block'; } else { link.style['font-weight'] = 'normal'; e.style.display = 'none'; } return false; } </script> |]

group register report items by transaction groupeditems [] = [] groupeditems items = is:(groupeditems js) where (is,js) = span (\(ds,_,_) -> isNothing ds) items

* html, body {height: 100%} * * #content {min-height: 100%} * * #editform textarea { height:100%; } *

* input:focus { background-color: #efe; } *

* a.tooltip {position: relative} * * a.tooltip span {display:none; padding:5px; width:200px;} * * a:hover {background:#fff;} /\*background-color is a must for IE6*\ / / a.tooltip:hover span{display:inline; position:absolute;} */

* div#page {width: 960px; margin: 0 auto} *

* div#container {height: 35px; line-height: 35px} *

* div#content {position: absolute; top: 50%; height: 500px; margin-top: -250px} *

* div#content {position: absolute; top: 50%; left:50%; width:800px; height: 500px; margin-left: -400px; margin-top: -250px} *

* div#button {background: #888; border: 1px solid; border-color: #999 #777 #777 #999 } *

* .element {border-radius: 5px} *

; prototype "equalising" transactions ; ; generate a transfer between alice & bob equalising their contribution to rent's 5/1 balance ; A 2010/5/1 expenses:rent ; alice 50% ; bob 50%

; generate a transfer between alice & bob such that alice's contribution to car payment's 5/1 balance is $100 ; A 2010/5/1 expenses🚗payment ; alice $100 ; bob

; A 2010/5/1 expenses:car not:expenses:car:payment ; alice 50% ; bob

; A 2010/5/1 expenses:food ; alice ; bob

; A 2010/5/1 expenses:home ; alice ; bob

; A 2010/5/1 expenses:utilities ; alice ; bob

maybeFileInput :: String -> FormInput sub master (Maybe FileInfo) maybeFileInput name = GForm $ \_ env -> do let res = FormSuccess $ lookup name env return (res, [addBody [$hamlet| %input!type=file!name=$name$

]], Multipart)

handler for add form auto-complete requests <?php header("Content-type:text/xml"); ini_set('max_execution_time', 600); require_once('../../common/config.php'); print("<?xml version=\"1.0\"?>");

$link = mysql_pconnect($mysql_host, $mysql_user, $mysql_pasw); $db = mysql_select_db ($mysql_db);

if (!isset($_GET["pos"])) $_GET["pos"]=0;

//Create database and table if doesn't exists //mysql_create_db($mysql_db,$link); $sql = "Select * from Countries"; -- $res = mysql_query ($sql); -- if(!$res){ $sql = "CREATE TABLE Countries (item_id INT UNSIGNED not null AUTO_INCREMENT,item_nm VARCHAR (200),item_cd VARCHAR (15),PRIMARY KEY ( item_id ))"; -- $res = mysql_query ($sql); -- populateDBRendom(); -- }else{ -- -- } -- //populate db with 10000 records -- function populateDBRendom(){ -- $filename = getcwd()."../../common/countries.txt"; $handle = fopen ($filename, "r"); $contents = fread ($handle, filesize ($filename)); -- $arWords = split("\r\n",$contents); -- //print(count($arWords)); for($i=0;$i<count($arWords);$i++){ $nm = $arWords[$i]; -- $cd = rand(123456,987654); $sql = "INsert into Countries(item_nm,item_cd) Values('".$nm."','".$cd."')"; -- mysql_query ($sql); if($i==9999) -- break; -- } -- fclose ($handle); }

getDataFromDB($_GET["mask"]); -- mysql_close($link);

//print one level of the tree, based on parent_id function getDataFromDB($mask){ -- $sql = "SELECT DISTINCT item_nm FROM Countries Where item_nm like '".mysql_real_escape_string($mask)."%'"; -- $sql.= " Order By item_nm LIMIT ". $_GET["pos"].",20";

if ( $_GET["pos"]==0) -- print("<complete>"); -- else -- print("<complete add='true'>"); -- $res = mysql_query ($sql); -- if($res){ while($row=mysql_fetch_array($res)){ print("<option value=\"".$row["item_nm"]."\">"); -- print($row["item_nm"]); print("</option>"); } }else{ echo mysql_errno().": ".mysql_error()." at ".__LINE__." line in ".__FILE__." file<br>"; } print("</complete>"); } ?>

linux binary linking issue

Linking bin/hledger-0.13-linux-x86_64 … /usr/local/lib/ghc-6.12.3/unix-2.4.0.2/libHSunix-2.4.0.2.a(HsUnix.o): In function `__hsunix_getpwent': HsUnix.c:(.text+0x171): warning: Using 'getpwent' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking /usr/local/lib/ghc-6.12.3/unix-2.4.0.2/libHSunix-2.4.0.2.a(HsUnix.o): In function `__hsunix_getpwnam_r': HsUnix.c:(.text+0x161): warning: Using 'getpwnam_r' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking /usr/local/lib/ghc-6.12.3/unix-2.4.0.2/libHSunix-2.4.0.2.a(HsUnix.o): In function `__hsunix_getpwuid_r': HsUnix.c:(.text+0x151): warning: Using 'getpwuid_r' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking

windows build issues

with cygwin 1.7.7, windows xp

process

$ (date && ghc version && cabal update && cabal configure && cabal build) >log 2>&1 Mon Dec 6 14:23:11 PST 2010 The Glorious Glasgow Haskell Compilation System, version 6.12.3 Downloading the latest package list from hackage.haskell.org Resolving dependencies… Configuring process-1.0.1.4… configure: WARNING: unrecognized options: with-compiler checking for gcc… gcc checking whether the C compiler works… yes checking for C compiler default output file name… a.exe checking for suffix of executables… .exe checking whether we are cross compiling… no checking for suffix of object files… o checking whether we are using the GNU C compiler… yes checking whether gcc accepts -g… yes checking for gcc option to accept ISO C89… none needed checking how to run the C preprocessor… gcc -E checking for grep that handles long lines and -e… /usr/bin/grep checking for egrep… /usr/bin/grep -E checking for ANSI C header files… yes checking for sys/types.h… yes checking for sys/stat.h… yes checking for stdlib.h… yes checking for string.h… yes checking for memory.h… yes checking for strings.h… yes checking for inttypes.h… yes checking for stdint.h… yes checking for unistd.h… yes checking for pid_t… yes checking vfork.h usability… no checking vfork.h presence… no checking for vfork.h… no checking for fork… yes checking for vfork… yes checking for working fork… yes checking for working vfork… (cached) yes checking signal.h usability… yes checking signal.h presence… yes checking for signal.h… yes checking sys/wait.h usability… yes checking sys/wait.h presence… yes checking for sys/wait.h… yes checking fcntl.h usability… yes checking fcntl.h presence… yes checking for fcntl.h… yes checking for setitimer,… no checking for sysconf… yes checking value of SIG_DFL… 0 checking value of SIG_IGN… 1 configure: creating ./config.status config.status: creating include/HsProcessConfig.h config.status: include/HsProcessConfig.h is unchanged configure: WARNING: unrecognized options: with-compiler Preprocessing library process-1.0.1.4… Building process-1.0.1.4… In file included from C:/HP/lib/base-4.2.0.2/include/HsBase.h:33,

from cbits\runProcess.c:12:0: C:/cygwin/usr/include/stdlib.h:110: warning: `__warning__' attribute directive ignored C:/cygwin/usr/include/stdlib.h:117: warning: `__warning__' attribute directive ignored In file included from C:/HP/mingw/bin/../lib/gcc/mingw32/3.4.5/../../../../include/windows.h:98, from C:/HP/lib/base-4.2.0.2/include/HsBase.h:88,

from cbits\runProcess.c:12:0: C:/HP/mingw/bin/../lib/gcc/mingw32/3.4.5/../../../../include/winsock2.h:103:2: warning: #warning "fd_set and associated macros have been defined in sys/types. This may cause runtime problems with W32 sockets"

In file included from cbits\runProcess.c:12:0: C:/HP/lib/base-4.2.0.2/include/HsBase.h: In function `__hscore_ftruncate': C:/HP/lib/base-4.2.0.2/include/HsBase.h:347: warning: implicit declaration of function `ftruncate' C:/HP/lib/base-4.2.0.2/include/HsBase.h: At top level: C:/HP/lib/base-4.2.0.2/include/HsBase.h:378: error: syntax error before "stsize_t" C:/HP/lib/base-4.2.0.2/include/HsBase.h:378: warning: type defaults to `int' in declaration of `stsize_t' C:/HP/lib/base-4.2.0.2/include/HsBase.h:378: warning: data definition has no type or storage class C:/HP/lib/base-4.2.0.2/include/HsBase.h: In function `__hscore_sizeof_stat': C:/HP/lib/base-4.2.0.2/include/HsBase.h:387: error: invalid application of `sizeof' to incomplete type `C:/HP/lib/base-4.2.0.2/include/HsBase.h' C:/HP/lib/base-4.2.0.2/include/HsBase.h: In function `__hscore_st_mtime': C:/HP/lib/base-4.2.0.2/include/HsBase.h:390: error: dereferencing pointer to incomplete type C:/HP/lib/base-4.2.0.2/include/HsBase.h: At top level: C:/HP/lib/base-4.2.0.2/include/HsBase.h:391: error: syntax error before "__hscore_st_size" C:/HP/lib/base-4.2.0.2/include/HsBase.h:391: warning: return type defaults to `int' C:/HP/lib/base-4.2.0.2/include/HsBase.h: In function `__hscore_st_size': C:/HP/lib/base-4.2.0.2/include/HsBase.h:391: error: dereferencing pointer to incomplete type C:/HP/lib/base-4.2.0.2/include/HsBase.h: In function `__hscore_st_mode': C:/HP/lib/base-4.2.0.2/include/HsBase.h:393: error: dereferencing pointer to incomplete type C:/HP/lib/base-4.2.0.2/include/HsBase.h: In function `__hscore_st_dev': C:/HP/lib/base-4.2.0.2/include/HsBase.h:394: error: dereferencing pointer to incomplete type C:/HP/lib/base-4.2.0.2/include/HsBase.h: In function `__hscore_st_ino': C:/HP/lib/base-4.2.0.2/include/HsBase.h:395: error: dereferencing pointer to incomplete type C:/HP/lib/base-4.2.0.2/include/HsBase.h: In function `__hscore_stat': C:/HP/lib/base-4.2.0.2/include/HsBase.h:400: warning: implicit declaration of function `_wstati64' C:/HP/lib/base-4.2.0.2/include/HsBase.h: In function `__hscore_fstat': C:/HP/lib/base-4.2.0.2/include/HsBase.h:404: warning: implicit declaration of function `_fstati64' C:/HP/lib/base-4.2.0.2/include/HsBase.h: In function `__hscore_open': C:/HP/lib/base-4.2.0.2/include/HsBase.h:591: warning: implicit declaration of function `_wsopen' C:/HP/lib/base-4.2.0.2/include/HsBase.h: At top level: C:/HP/lib/base-4.2.0.2/include/HsBase.h:608: error: syntax error before "__hscore_lseek" C:/HP/lib/base-4.2.0.2/include/HsBase.h:608: error: syntax error before "off64_t" C:/HP/lib/base-4.2.0.2/include/HsBase.h:608: warning: return type defaults to `int' C:/HP/lib/base-4.2.0.2/include/HsBase.h: In function `__hscore_lseek': C:/HP/lib/base-4.2.0.2/include/HsBase.h:609: warning: implicit declaration of function `_lseeki64' C:/HP/lib/base-4.2.0.2/include/HsBase.h:609: error: `fd' undeclared (first use in this function) C:/HP/lib/base-4.2.0.2/include/HsBase.h:609: error: (Each undeclared identifier is reported only once C:/HP/lib/base-4.2.0.2/include/HsBase.h:609: error: for each function it appears in.) C:/HP/lib/base-4.2.0.2/include/HsBase.h:609: error: `off' undeclared (first use in this function) C:/HP/lib/base-4.2.0.2/include/HsBase.h:609: error: `whence' undeclared (first use in this function) cbits\runProcess.c: In function `runInteractiveProcess':

cbits\runProcess.c:387:0: warning: implicit declaration of function `_get_osfhandle'

cbits\runProcess.c:463:0: warning: implicit declaration of function `_open_osfhandle'

haskeline

$ (date && ghc version && cabal update && cabal install haskeline) >log 2>&1 Mon Dec 6 14:39:54 PST 2010 The Glorious Glasgow Haskell Compilation System, version 6.12.3 Downloading the latest package list from hackage.haskell.org Resolving dependencies… [1 of 1] Compiling Main ( C:\DOCUME~1§IMON\LOCALS~1\Temp\haskeline-0.6.3.24132\haskeline-0.6.3.2§etup.hs, C:\DOCUME~1§IMON\LOCALS~1\Temp\haskeline-0.6.3.24132\haskeline-0.6.3.2\dist\setup\Main.o ) Linking C:\DOCUME~1§IMON\LOCALS~1\Temp\haskeline-0.6.3.24132\haskeline-0.6.3.2\dist\setup\setup.exe … Configuring haskeline-0.6.3.2… Preprocessing library haskeline-0.6.3.2… In file included from C:/HP/mingw/bin/../lib/gcc/mingw32/3.4.5/../../../../include/windows.h:98, from includes/win_console.h:3, from System\Console\Haskeline\Backend\Win32.hsc:27: C:/HP/mingw/bin/../lib/gcc/mingw32/3.4.5/../../../../include/winsock2.h:103:2: warning: #warning "fd_set and associated macros have been defined in sys/types. This may cause runtime problems with W32 sockets" dist\build§ystem\Console\Haskeline\Backend\Win32_hsc_make.o:Win32_hsc_make.c:(.text+0x47): undefined reference to `_impure_ptr' dist\build§ystem\Console\Haskeline\Backend\Win32_hsc_make.o:Win32_hsc_make.c:(.text+0x7b): undefined reference to `_impure_ptr' dist\build§ystem\Console\Haskeline\Backend\Win32_hsc_make.o:Win32_hsc_make.c:(.text+0x93): undefined reference to `_impure_ptr' dist\build§ystem\Console\Haskeline\Backend\Win32_hsc_make.o:Win32_hsc_make.c:(.text+0xc7): undefined reference to `_impure_ptr' dist\build§ystem\Console\Haskeline\Backend\Win32_hsc_make.o:Win32_hsc_make.c:(.text+0xf3): undefined reference to `_impure_ptr' dist\build§ystem\Console\Haskeline\Backend\Win32_hsc_make.o:Win32_hsc_make.c:(.text+0x127): more undefined references to `_impure_ptr' follow collect2: ld returned 1 exit status linking dist\build§ystem\Console\Haskeline\Backend\Win32_hsc_make.o failed command was: C:\HPmingw\bin\gcc.exe -LC:\cygwin\lib -LC:\Documents and Settings§imon\Application Data\cabal\utf8-string-0.3.6\ghc-6.12.3 -LC:\cygwin\lib -LC:\HP\lib\extralibs\mtl-1.1.0.2\ghc-6.12.3 -LC:\HP\lib\extensible-exceptions-0.1.1.1 -LC:\HP\lib\directory-1.0.1.1 -LC:\HP\lib\old-time-1.0.0.5 -LC:\HP\lib\old-locale-1.0.0.2 -LC:\HP\lib\filepath-1.1.0.4 -LC:\HP\lib\containers-0.3.0.0 -LC:\HP\lib\base-3.0.3.2 -LC:\HP\lib\syb-0.1.0.2 -LC:\HP\lib\array-0.3.0.1 -LC:\HP\lib\Win32-2.2.0.2 -luser32 -lgdi32 -lwinmm -ladvapi32 -lshell32 -lshfolder -LC:\HP\lib\bytestring-0.9.1.7 -LC:\HP\lib\base-4.2.0.2 -lwsock32 -luser32 -lshell32 -LC:\HP\lib∫eger-gmp-0.2.0.1 -LC:\HP\lib\ghc-prim-0.2.0.0 -LC:\HP\lib -LC:\HP\lib/gcc-lib -lm -lwsock32 -LC:\HP\lib dist\build§ystem\Console\Haskeline\Backend\Win32_hsc_make.o -o dist\build§ystem\Console\Haskeline\Backend\Win32_hsc_make.exe cabal.exe: Error: some packages failed to install: haskeline-0.6.3.2 failed during the building phase. The exception was: ExitFailure 1

gtk2hs

Eduard_Munteanu> sm: gtk2hs-0.10.1 (binary), HP 2009.2.0.1 (binary too) if you ever need it.

wine on osx 10.6

enumerator

Z:\Userson\src\hledger-win\hledger-web>cabal install Resolving dependencies… Configuring enumerator-0.4.14… Preprocessing library enumerator-0.4.14… Building enumerator-0.4.14… [1 of 8] Compiling Data.Enumerator.Util ( lib\Data\Enumerator\Util.hs, dist\build\Data\Enumerator\Util.o ) [2 of 8] Compiling Data.Enumerator[boot] ( lib\Data\Enumerator.hs-boot, dist\build\Data\Enumerator.o-boot )

(dialog: The program touchy.exe has encountered a serious problem and needs to close…)

Unhandled exception: page fault on read access to 0x00000000 in 32-bit code (0x406abacb). Register dump: CS:0017 SS:001f DS:001f ES:001f FS:1007 GS:0037 EIP:406abacb ESP:0060fda0 EBP:0060fdc8 EFLAGS:00010206( R- I - -P- ) EAX:00000000 EBX:00000001 ECX:0060fde0 EDX:00000000 ESI:00110449 EDI:00000002 Stack dump: 0x0060fda0: 00110428 00000003 40699385 406a7cc2 0x0060fdb0: 00110449 00111880 40699721 406a837f 0x0060fdc0: 0060fde0 00110428 0060fdf8 406abb22 0x0060fdd0: 0000000d 406ec660 40701064 406abb22 0x0060fde0: 00110449 00000000 0060fe10 00000001 0x0060fdf0: 00110428 0060fe10 0060fe88 00401492 0200: sel=1007 base=7ffc0000 limit=00000fff 32-bit rw- Backtrace: =>0 0x406abacb __utime32+0x1b() in msvcrt (0x0060fdc8) 1 0x406abb22 __utime+0x21() in msvcrt (0x0060fdf8) 2 0x00401492 in touchy (+0x1491) (0x0060fe88) 3 0x0040124b in touchy (+0x124a) (0x0060fec0) 4 0x00401298 in touchy (+0x1297) (0x0060fed0) 5 0x7b84ecec _call_process_entry+0xb() in kernel32 (0x0060fee8) 6 0x7b8517a6 _start_process+0x65() in kernel32 (0x0060ff28) 7 0x7bc64eac _call_thread_func+0xb() in ntdll (0x0060ff48) 8 0x7bc65b2a _call_thread_entry_point+0x79() in ntdll (0x0060ffc8) 9 0x7bc3d98e _start_process+0x1d() in ntdll (0x0060ffe8) 0x406abacb __utime32+0x1b in msvcrt: movl 0x0(%edx),%eax Modules: Module Address Debug info Name (18 modules) ELF 0- 6101000 Stabs <wine-loader> PE 400000- 407000 Deferred touchy PE 40000000-40148000 Stabs libwine.1.dylib ELF 40682000-40734000 Stabs msvcrt<elf> \-PE 40690000-406ed000 \ msvcrt ELF 7b800000-7b929000 Stabs kernel32<elf> \-PE 7b810000-7b8da000 \ kernel32 ELF 7bc00000-7bce7000 Stabs ntdll<elf> \-PE 7bc10000-7bc98000 \ ntdll PE 912bd000-9137e000 Deferred libobjc.a.dylib PE 92c33000-92c46000 Deferred libz.1.dylib PE 936bb000-93738000 Deferred iokit PE 93c49000-93c9c000 Deferred libauto.dylib PE 93f63000-941d6000 Deferred corefoundation PE 94336000-94346000 Deferred libkxld.dylib PE 945fe000-947e2000 Deferred libicucore.a.dylib PE 988fc000-98902000 Deferred libmathcommon.a.dylib PE 99ada000-99d03000 Deferred libsystem.b.dylib Threads: process tid prio (all id:s are in hex) 0000000e services.exe 0000001f 0 00000016 0 00000010 0 0000000f 0 00000011 winedevice.exe 0000001b 0 00000019 0 00000015 0 00000012 0 00000013 explorer.exe 00000014 0 0000001c plugplay.exe 00000020 0 0000001e 0 0000001d 0 00000043 wineconsole.exe 00000042 0 0000003d cmd.exe 00000045 0 00000035 wineconsole.exe 0000000b 0 0000002f cmd.exe 0000002c 0 00000041 cabal.EXE 00000046 0 00000033 0 0000003b 0 00000034 0 0000001a ghc.exe 00000029 0 00000024 0 00000028 0 0000003c 0 00000040 0 00000022 0 0000000d (D) C:\HP\lib→uchy.exe 00000030 0 <== Backtrace: =>0 0x406abacb __utime32+0x1b() in msvcrt (0x0060fdc8) 1 0x406abb22 __utime+0x21() in msvcrt (0x0060fdf8) 2 0x00401492 in touchy (+0x1491) (0x0060fe88) 3 0x0040124b in touchy (+0x124a) (0x0060fec0) 4 0x00401298 in touchy (+0x1297) (0x0060fed0) 5 0x7b84ecec _call_process_entry+0xb() in kernel32 (0x0060fee8) 6 0x7b8517a6 _start_process+0x65() in kernel32 (0x0060ff28) 7 0x7bc64eac _call_thread_func+0xb() in ntdll (0x0060ff48) 8 0x7bc65b2a _call_thread_entry_point+0x79() in ntdll (0x0060ffc8) 9 0x7bc3d98e _start_process+0x1d() in ntdll (0x0060ffe8) cabal: Error: some packages failed to install: enumerator-0.4.14 failed during the building phase. The exception was: ExitFailure (-1073741819)

good list of cost of ownership questions

writing tips

tekmo

orig

http://www.reddit.com/r/haskell/comments/19jbz5/how_to_cabal_install_a_new_tutorial/

Since you're practicing your writing, I'll give some tips:

> cabal is a command-line program for downloading and building > software written in Haskell. It can install all kinds of fascinating > and useful software packages from the Hackage repository. It is > excellent and indispensable, but it currently has a troublesome > flaw: it sometimes mysteriously refuses to install things, leading > to cries of "Aaagh! cabal hell!!".

The above paragraph keeps referring back to cabal as it, which increases the reader's cognitive load. The reader must retain the first sentence in memory to understand the rest of the paragraph, perhaps referring back to it if they already flushed the first sentence from memory. A well-written article resembles an efficient program: you strive to stream all the information in as little memory as possible so that the reader can ideally use the smallest and most efficient cache while reading.

> A little extra know-how prevents this. This tutorial aims to show > you how to install cabal packages with confidence, especially if you > are new to Cabal and Haskell. Welcome and let's get started!

The second paragraph repeats the same error as the first paragraph. Your first this refers to something in the previous paragraph, which prevents the user from mentally freeing the former paragraph. Every paragraph should make sense in isolation if you want to improve readability.

> Your system may have a package manager, like apt-get, yum, or > macports, and it might offer packages for the Haskell software you > want to install. In this case you may save time by using it instead > of cabal. It probably offers more stable, better-integrated > packages, and they may be pre-compiled.

Every paragraph's first sentence should serve as an abstract for that paragraph. Readers use the first sentence of each paragraph to judge whether or not to read it. In fact, well-written essays will still read well if you just replace each paragraph with its first sentence.

> In short: this tutorial is about using cabal-install, which is cabal > on the command line.

Your summary sentence belongs in your first paragraph. The first paragraph behaves like an abstract for the rest of the article.

> It is often available as a system package, otherwise get it by > installing the Haskell Platform, or just GHC.

Avoid passive tense as much as possible, because it requires the reader to infer the actor in the sentence, increasing their cognitive load. For example, you could rephrase the above sentence as "System package managers often provide cabal, but you can also obtain it from the Haskell Platform".

> To check that it's installed, at a command prompt do:

Get to the verb of a sentence within about 7-ish words, the earlier the better. Sentences resemble thunks, and you cannot force the thunk until you get to the verb. You actually stick to this rule pretty well, although you lapse a few times throughout the article.

You also do several things very well:

  • You motivate everything you teach by introducing each topic as the solution to a specific, practical problem.
  • You emphasize showing the reader rather than telling them.

Finally, spend lots of time rewriting for articles that you care a lot about. I find that my most well-received posts are the ones I rewrite repeatedly over a week. You always view your own writing with fresh eyes after every full night's rest.

summary
The above paragraph keeps referring back to cabal as it, which increases the reader's cognitive load.
Every paragraph should make sense in isolation if you want to improve readability.
Every paragraph's first sentence should serve as an abstract for that paragraph.
The first paragraph behaves like an abstract for the rest of the article.
Avoid passive tense as much as possible, because it requires the reader to infer the actor in the sentence, increasing their cognitive load.
Get to the verb of a sentence within about 7-ish words, the earlier the better.
Spend lots of time rewriting for articles that you care a lot about
You always view your own writing with fresh eyes after every full night's rest.

good list of cost of ownership questions

log

partial activity log

2010

5/4

balance sheet pomodoro 1

started balance sheet script began refactoring for importable Hledger.Cli.* set up missing tools on netbook: haskell-mode adapt to distro & ghc 6.12 upgrade install missing cabal packages tighten dependency to avoid testpack 2.0 api change ghc-pkg dump error

balance sheet pomodoro 2

set up work log adapt to distro & ghc 6.12 upgrade: ghc-pkg dump error (cabal clean) tools setup: hasktags move Options to Hledger.Cli got trivial balancesheet script working deal with darcs mv screwup

5/6

review/cleanup pomodoro

review/record pending changes develop work log/backlog website hakyll conversion

5/19

researched current web libs finished move to Hledger module space cleaned up notes

5/20

converted manual to markdown more detailed installation docs

5/21

upgraded hakyll fixed hakyll/pandoc quotes issue

5/22

refactored journal/ledger construction updated benchmarks resolved register memory leak

5/23

clarified Journal & Ledger roles various 6.12, utf8 and other fixes released 0.10

5/24

implemented flat, drop

5/25

support, investigated rounding issue

2011

optionsgeddon oh my god

old help

Usage: hledger [OPTIONS] COMMAND [PATTERNS] hledger [OPTIONS] convert CSVFILE

Reads your ~/.journal file, or another specified by $LEDGER or -f, and runs the specified command (may be abbreviated):

add - prompt for new transactions and add them to the journal balance - show accounts, with balances convert - show the specified CSV file as a hledger journal histogram - show a barchart of transactions per day or other interval print - show transactions in journal format register - show transactions as a register with running balance stats - show various statistics for a journal test - run self-tests

hledger options: -f FILE file=FILE use a different journal/timelog file; - means stdin no-new-accounts don't allow to create new accounts -b DATE begin=DATE report on transactions on or after this date -e DATE end=DATE report on transactions before this date -p EXPR period=EXPR report on transactions during the specified period and/or with the specified reporting interval -C cleared report only on cleared transactions -U uncleared report only on uncleared transactions -B cost, basis report cost of commodities depth=N hide accounts/transactions deeper than this -d EXPR display=EXPR show only transactions matching EXPR (where EXPR is 'dOP[DATE]' and OP is <, <=, , >, >) effective use transactions' effective dates, if any -E empty show empty/zero things which are normally elided -R real report only on real (non-virtual) transactions flat balance: show full account names, unindented drop=N balance: with flat, elide first N account name components no-total balance: hide the final total -D daily register, stats: report by day -W weekly register, stats: report by week -M monthly register, stats: report by month -Q quarterly register, stats: report by quarter -Y yearly register, stats: report by year -v verbose show more verbose output debug show extra debug output; implies verbose binary-filename show the download filename for this hledger build -V version show version information -h help show command-line usage

DATES can be y/m/d or smart dates like "last month". PATTERNS are regular expressions which filter by account name. Prefix a pattern with desc: to filter by transaction description instead, prefix with not: to negate it. When using both, not: comes last.

attempts
original getopts

progname_cli = "hledger"

| The program name which, if we are invoked as (via symlink or renaming), causes us to default to reading the user's time log instead of their journal. progname_cli_time = "hours"

usage_preamble_cli = "Usage: hledger [OPTIONS] COMMAND [PATTERNS]\n" + " hledger [OPTIONS] convert CSVFILE\n" + "\n" + "Reads your ~/.journal file, or another specified by $LEDGER or -f, and\n" + "runs the specified command (may be abbreviated):\n" + "\n" + " add - prompt for new transactions and add them to the journal\n" + " balance - show accounts, with balances\n" + " convert - show the specified CSV file as a hledger journal\n" + " histogram - show a barchart of transactions per day or other interval\n" + " print - show transactions in journal format\n" + " register - show transactions as a register with running balance\n" + " stats - show various statistics for a journal\n" + " test - run self-tests\n" + "\n"

usage_options_cli = usageInfo "hledger options:" options_cli

usage_postscript_cli = "\n" + "DATES can be y/m/d or smart dates like \"last month\". PATTERNS are regular\n" + "expressions which filter by account name. Prefix a pattern with desc: to\n" + "filter by transaction description instead, prefix with not: to negate it.\n" + "When using both, not: comes last.\n"

usage_cli = concat [ usage_preamble_cli ,usage_options_cli ,usage_postscript_cli ]

| Command-line options we accept. options_cli :: [OptDescr Opt] options_cli = [ Option "f" ["file"] (ReqArg File "FILE") "use a different journal/timelog file; - means stdin" ,Option "b" ["begin"] (ReqArg Begin "DATE") "report on transactions on or after this date" ,Option "e" ["end"] (ReqArg End "DATE") "report on transactions before this date" ,Option "p" ["period"] (ReqArg Period "EXPR") ("report on transactions during the specified period\n" ++ "and/or with the specified reporting interval\n") ,Option "C" ["cleared"] (NoArg Cleared) "report only on cleared transactions" ,Option "U" ["uncleared"] (NoArg UnCleared) "report only on uncleared transactions" ,Option "B" ["cost","basis"] (NoArg CostBasis) "report cost of commodities" ,Option "" ["alias"] (ReqArg Alias "ACCT=ALIAS") "display ACCT's name as ALIAS in reports" ,Option "" ["depth"] (ReqArg Depth "N") "hide accounts/transactions deeper than this" ,Option "d" ["display"] (ReqArg Display "EXPR") ("show only transactions matching EXPR (where\n" ++ "EXPR is 'dOP[DATE]' and OP is <, <=, , >, >)") ,Option "" ["effective"] (NoArg Effective) "use transactions' effective dates, if any" ,Option "E" ["empty"] (NoArg Empty) "show empty/zero things which are normally elided" ,Option "" ["no-elide"] (NoArg NoElide) "no eliding at all, stronger than -E (eg for balance report)" ,Option "R" ["real"] (NoArg Real) "report only on real (non-virtual) transactions" ,Option "" ["flat"] (NoArg Flat) "balance: show full account names, unindented" ,Option "" ["drop"] (ReqArg Drop "N") "balance: with flat, elide first N account name components" ,Option "" ["no-total"] (NoArg NoTotal) "balance: hide the final total" ,Option "D" ["daily"] (NoArg DailyOpt) "register, stats: report by day" ,Option "W" ["weekly"] (NoArg WeeklyOpt) "register, stats: report by week" ,Option "M" ["monthly"] (NoArg MonthlyOpt) "register, stats: report by month" ,Option "Q" ["quarterly"] (NoArg QuarterlyOpt) "register, stats: report by quarter" ,Option "Y" ["yearly"] (NoArg YearlyOpt) "register, stats: report by year" ,Option "" ["no-new-accounts"] (NoArg NoNewAccts) "add: don't allow creating new accounts" ,Option "r" ["rules"] (ReqArg RulesFile "FILE") "convert: rules file to use (default:JOURNAL.rules)" ,Option "F" ["format"] (ReqArg ReportFormat "STR") "use STR as the format" ,Option "v" ["verbose"] (NoArg Verbose) "show more verbose output" ,Option "" ["debug"] (NoArg Debug) "show extra debug output; implies verbose" ,Option "" ["binary-filename"] (NoArg BinaryFilename) "show the download filename for this hledger build" ,Option "V" ["version"] (NoArg Version) "show version information" ,Option "h" ["help"] (NoArg Help) "show command-line usage" ]

| An option value from a command-line flag. data Opt = File {value::String}

NoNewAccts
Begin {value::String}
End {value::String}
Period {value::String}
Cleared
UnCleared
CostBasis
Alias {value::String}
Depth {value::String}
Display {value::String}
Effective
Empty
NoElide
Real
Flat
Drop {value::String}
NoTotal
DailyOpt
WeeklyOpt
MonthlyOpt
QuarterlyOpt
YearlyOpt
RulesFile {value::String}
ReportFormat {value::String}
Help
Verbose
Version
BinaryFilename
Debug

XXX add-on options, must be defined here for now vty

DebugVty

web

BaseUrl {value::String}
Port {value::String}

chart

ChartOutput {value::String}
ChartItems {value::String}
ChartSize {value::String}

deriving (Show,Eq)

these make me nervous optsWithConstructor f opts = concatMap get opts where get o = [o | f v == o] where v = value o

optsWithConstructors fs opts = concatMap get opts where get o = [o | any (== o) fs]

optValuesForConstructor f opts = concatMap get opts where get o = [v | f v == o] where v = value o

optValuesForConstructors fs opts = concatMap get opts where get o = [v | any (\f -> f v == o) fs] where v = value o

| Parse the command-line arguments into options and arguments using the specified option descriptors. Any smart dates in the options are converted to explicit YYYY/MM/DD format based on the current time. If parsing fails, raise an error, displaying the problem along with the provided usage string. parseArgumentsWith :: [OptDescr Opt] -> IO ([Opt], [String]) parseArgumentsWith options = do rawargs <- map fromPlatformString `fmap` getArgs parseArgumentsWith' options rawargs

parseArgumentsWith' options rawargs = do let (opts,args,errs) = getOpt Permute options rawargs opts' <- fixOptDates opts let opts'' = if Debug `elem` opts' then Verbose:opts' else opts' if null errs then return (opts'',args) else argsError (concat errs) >> return ([],[])

argsError :: String -> IO () argsError = ioError . userError' . (++ " Run with help to see usage.")

| Convert any fuzzy dates within these option values to explicit ones, based on today's date. fixOptDates :: [Opt] -> IO [Opt] fixOptDates opts = do d <- getCurrentDay return $ map (fixopt d) opts where fixopt d (Begin s) = Begin $ fixSmartDateStr d s fixopt d (End s) = End $ fixSmartDateStr d s fixopt d (Display s) = -- hacky Display $ regexReplaceBy "\\[.+?\\]" fixbracketeddatestr s where fixbracketeddatestr s = "[" + fixSmartDateStr d (init $ tail s) + "]" fixopt _ o = o

| Figure out the overall date span we should report on, based on any begin/end/period options provided. If there is a period option, the others are ignored. dateSpanFromOpts :: Day -> [Opt] -> DateSpan dateSpanFromOpts refdate opts

not (null popts) = case parsePeriodExpr refdate $ last popts of

Right (_, s) -> s Left e -> parseerror e

otherwise = DateSpan lastb laste

where popts = optValuesForConstructor Period opts bopts = optValuesForConstructor Begin opts eopts = optValuesForConstructor End opts lastb = listtomaybeday bopts laste = listtomaybeday eopts listtomaybeday vs = if null vs then Nothing else Just $ parse $ last vs where parse = parsedate . fixSmartDateStr refdate

| Figure out the reporting interval, if any, specified by the options. If there is a period option, the others are ignored. intervalFromOpts :: [Opt] -> Interval intervalFromOpts opts = case (periodopts, intervalopts) of ((p:_), _) -> case parsePeriodExpr (parsedate "0001/01/01") p of Right (i, _) -> i Left e -> parseerror e (, (DailyOpt:)) -> Days 1 (, (WeeklyOpt:)) -> Weeks 1 (, (MonthlyOpt:)) -> Months 1 (, (QuarterlyOpt:)) -> Quarters 1 (, (YearlyOpt:)) -> Years 1 (_, _) -> NoInterval where periodopts = reverse $ optValuesForConstructor Period opts intervalopts = reverse $ filter (`elem` [DailyOpt,WeeklyOpt,MonthlyOpt,QuarterlyOpt,YearlyOpt]) opts

rulesFileFromOpts :: [Opt] -> Maybe FilePath rulesFileFromOpts opts = listtomaybe $ optValuesForConstructor RulesFile opts where listtomaybe [] = Nothing listtomaybe vs = Just $ head vs

| Default balance format string: "%20(total) %2(depth_spacer)%-(account)" defaultBalanceFormatString :: [FormatString] defaultBalanceFormatString = [ FormatField False (Just 20) Nothing Total , FormatLiteral " " , FormatField True (Just 2) Nothing DepthSpacer , FormatField True Nothing Nothing Format.Account ]

| Parses the format string to either an error message or a format string. parseFormatFromOpts :: [Opt] -> Either String [FormatString] parseFormatFromOpts opts = listtomaybe $ optValuesForConstructor ReportFormat opts where listtomaybe :: [String] -> Either String [FormatString] listtomaybe [] = Right defaultBalanceFormatString listtomaybe vs = parseFormatString $ head vs

| Returns the format string. If the string can't be parsed it fails with error'. formatFromOpts :: [Opt] -> [FormatString] formatFromOpts opts = case parseFormatFromOpts opts of Left err -> error' err Right format -> format

| Get the value of the (last) depth option, if any. depthFromOpts :: [Opt] -> Maybe Int depthFromOpts opts = listtomaybeint $ optValuesForConstructor Depth opts where listtomaybeint [] = Nothing listtomaybeint vs = Just $ read $ last vs

| Get the value of the (last) drop option, if any, otherwise 0. dropFromOpts :: [Opt] -> Int dropFromOpts opts = fromMaybe 0 $ listtomaybeint $ optValuesForConstructor Drop opts where listtomaybeint [] = Nothing listtomaybeint vs = Just $ read $ last vs

| Get the value of the (last) display option, if any. displayExprFromOpts :: [Opt] -> Maybe String displayExprFromOpts opts = listtomaybe $ optValuesForConstructor Display opts where listtomaybe [] = Nothing listtomaybe vs = Just $ last vs

| Get the value of the (last) baseurl option, if any. baseUrlFromOpts :: [Opt] -> Maybe String baseUrlFromOpts opts = listtomaybe $ optValuesForConstructor BaseUrl opts where listtomaybe [] = Nothing listtomaybe vs = Just $ last vs

| Get the value of the (last) port option, if any. portFromOpts :: [Opt] -> Maybe Int portFromOpts opts = listtomaybeint $ optValuesForConstructor Port opts where listtomaybeint [] = Nothing listtomaybeint vs = Just $ read $ last vs

| Get a maybe boolean representing the last cleared/uncleared option if any. clearedValueFromOpts opts | null os = Nothing

last os == Cleared = Just True
otherwise = Just False

where os = optsWithConstructors [Cleared,UnCleared] opts

| Detect which date we will report on, based on effective. whichDateFromOpts :: [Opt] -> WhichDate whichDateFromOpts opts = if Effective `elem` opts then EffectiveDate else ActualDate

| Were we invoked as \"hours\" ? usingTimeProgramName :: IO Bool usingTimeProgramName = do progname <- getProgName return $ map toLower progname == progname_cli_time

| Get the journal file path from options, an environment variable, or a default journalFilePathFromOpts :: [Opt] -> IO String journalFilePathFromOpts opts = do istimequery <- usingTimeProgramName f <- if istimequery then myTimelogPath else myJournalPath return $ last $ f : optValuesForConstructor File opts

aliasesFromOpts :: [Opt] -> [(AccountName,AccountName)] aliasesFromOpts opts = map parseAlias $ optValuesForConstructor Alias opts where -- similar to ledgerAlias parseAlias :: String -> (AccountName,AccountName) parseAlias s = (accountNameWithoutPostingType $ strip orig ,accountNameWithoutPostingType $ strip alias') where (orig, alias) = break (='') s alias' = case alias of ('=':rest) -> rest _ -> orig

| Gather filter pattern arguments into a list of account patterns and a list of description patterns. We interpret pattern arguments as follows: those prefixed with "desc:" are description patterns, all others are account patterns; also patterns prefixed with "not:" are negated. not: should come after desc: if both are used. parsePatternArgs :: [String] -> ([String],[String]) parsePatternArgs args = (as, ds') where descprefix = "desc:" (ds, as) = partition (descprefix `isPrefixOf`) args ds' = map (drop (length descprefix)) ds

| Convert application options to the library's generic filter specification. optsToFilterSpec :: [Opt] -> [String] -> Day -> FilterSpec optsToFilterSpec opts args d = FilterSpec { datespan=dateSpanFromOpts d opts ,cleared=clearedValueFromOpts opts ,real=Real `elem` opts ,empty=Empty `elem` opts ,acctpats=apats ,descpats=dpats ,depth = depthFromOpts opts } where (apats,dpats) = parsePatternArgs args

currentLocalTimeFromOpts opts = listtomaybe $ optValuesForConstructor CurrentLocalTime opts -- where -- listtomaybe [] = Nothing -- listtomaybe vs = Just $ last vs

tests_Hledger_Cli_Options = TestList [ "dateSpanFromOpts" ~: do let todaysdate = parsedate "2008/11/26" let gives = is . show . dateSpanFromOpts todaysdate [] `gives` "DateSpan Nothing Nothing" [Begin "2008", End "2009"] `gives` "DateSpan (Just 2008-01-01) (Just 2009-01-01)" [Period "in 2008"] `gives` "DateSpan (Just 2008-01-01) (Just 2009-01-01)" [Begin "2005", End "2007",Period "in 2008"] `gives` "DateSpan (Just 2008-01-01) (Just 2009-01-01)"

,"intervalFromOpts" ~: do let gives = is . intervalFromOpts [] `gives` NoInterval [DailyOpt] `gives` Days 1 [WeeklyOpt] `gives` Weeks 1 [MonthlyOpt] `gives` Months 1 [QuarterlyOpt] `gives` Quarters 1 [YearlyOpt] `gives` Years 1 [Period "weekly"] `gives` Weeks 1 [Period "monthly"] `gives` Months 1 [Period "quarterly"] `gives` Quarters 1 [WeeklyOpt, Period "yearly"] `gives` Years 1

]

cmdargs implicit ADT with ifdefs

progname = "hledger" progversion = progversionstr progname

progname_cli = progname

| The program name which, if we are invoked as (via symlink or renaming), causes us to default to reading the user's time log instead of their journal. progname_cli_time = "hours"

usage_preamble = "Usage: hledger [OPTIONS] COMMAND [PATTERNS]\n" + " hledger [OPTIONS] convert CSVFILE\n" + "\n" + "Reads your ~/.journal file, or another specified by $LEDGER or -f, and\n" + "runs the specified command (may be abbreviated):\n" + "\n" + " add - prompt for new transactions and add them to the journal\n" + " balance - show accounts, with balances\n" + " convert - show the specified CSV file as a hledger journal\n" + " histogram - show a barchart of transactions per day or other interval\n" + " print - show transactions in journal format\n" + " register - show transactions as a register with running balance\n" + " stats - show various statistics for a journal\n" + " test - run self-tests\n" + "\n"

usage_postscript = [ "DATES can be y/m/d or smart dates like \"last month\". PATTERNS are regular" ,"expressions which filter by account name. Prefix a pattern with desc: to" ,"filter by transaction description instead, prefix with not: to negate it." ,"When using both, not: comes last." ]

| Command-line options & arguments we accept. data Opts = Add { } | Balance { } Convert { } Histogram { } Print { } Register { } Stats { } Test { } data Opts = Opts { :: hledger options file :: Maybe FilePath :: hledger-lib options ,begin :: Maybe String ,end :: Maybe String ,period :: Maybe String ,cleared_ :: Bool ,uncleared :: Bool ,cost :: Bool ,depth_ :: Maybe Int ,display :: Maybe String ,effective :: Bool ,empty_ :: Bool ,no_elide :: Bool ,real_ :: Bool ,flat :: Bool ,drop_ :: Int ,no_total :: Bool ,daily :: Bool ,weekly :: Bool ,monthly :: Bool ,quarterly :: Bool ,yearly :: Bool ,format :: Maybe String :: hledger options ,alias :: [String] ,no_new_accounts :: Bool ,rules_file :: Maybe String ,binary_filename :: Bool ,debug :: Bool

:: add-ons' options, must be defined here for now #ifdef HLEDGERVTY ,debug_vty :: Bool #endif #ifdef HLEDGERWEB ,base_url :: Maybe String ,port :: Maybe Int #endif #ifdef HLEDGERCHART ,chart_output :: Maybe String ,chart_items :: Maybe Int ,chart_size :: Maybe String #endif

,args_ :: [String]

} deriving (Show, Data, Typeable)

deriving instance Default Day instance Default DateSpan where def = nulldatespan instance Default Interval where def = NoInterval

defopts = Opts { = hledger options file = def &= name "f" &= typFile &= help "use a different journal file; - means stdin" = hledger-lib options ,begin = def &= name "b" &= typ "DATE" &= help "report on transactions on or after this date" ,end = def &= name "e" &= typ "DATE" &= help "report on transactions before this date" ,period = def &= typ "PERIODEXPR" &= help "report on transactions during the specified period and/or with the specified reporting interval" ,cleared_ = def &= name "c" &= help "report only on cleared transactions" ,uncleared = def &= name "u" &= help "report only on uncleared transactions" ,cost = def &= name "B" &= help "report cost of commodities" ,depth_ = def &= typ "N" &= help "hide accounts/transactions deeper than this" ,display = def &= typ "DISPLAYEXPR" &= name "d" &= help "show only transactions matching expr (where expr is 'dop[date]' and op is <, <=, , >, >)" ,effective = def &= help "use transactions' effective dates, if any" ,empty_ = def &= name "E" &= help "show empty/zero things which are normally elided" ,no_elide = def &= help "no eliding at all, stronger than -e (eg for balance report)" ,real_ = def &= name "r" &= help "report only on real (non-virtual) transactions" ,flat = def &= help "balance: show full account names, unindented" ,drop_ = def &= typ "N" &= help "balance: with flat, omit this many leading account name components" ,no_total = def &= help "balance: hide the final total" ,daily = def &= name "D" &= help "register, stats: report by day" ,weekly = def &= name "W" &= help "register, stats: report by week" ,monthly = def &= name "M" &= help "register, stats: report by month" ,quarterly = def &= name "Q" &= help "register, stats: report by quarter" ,yearly = def &= name "Y" &= help "register, stats: report by year" ,format = def &= typ "FORMATSTR" &= name "F" &= help "use this custom line format in reports" = hledger options ,alias = def &= typ "ACCT=ALIAS" &= help "display ACCT's name as ALIAS in reports" ,no_new_accounts = def &= help "add: don't allow creating new accounts" ,rules_file = def &= typFile &= help "convert: rules file to use (default: CSVFILE.rules)" ,binary_filename = def &= help "show the download filename for this hledger build, and exit" ,debug = def &= help "show extra debug output; implies verbose" ,args_ = def &= args &= typ "COMMAND [PATTERNS]" = add-ons' options, must be defined here for now #ifdef HLEDGERVTY ,debug_vty = def #endif #ifdef HLEDGERWEB ,base_url = def ,port = def #endif #ifdef HLEDGERCHART ,chart_output = def ,chart_items = def ,chart_size = def #endif } &= verbosity &= program progname &= summary progversion &= details usage_postscript

pre-decoding would be easier but doesn't work at least with ghc 6.12/cmdargs 0.7/unix: getArgsDecoded = map fromPlatformString `fmap` getArgs getHledgerOpts = getArgsDecoded >>= flip withArgs (cmdArgs defopts) >>= processOpts >>= checkOpts getHledgerOpts :: IO Opts getHledgerOpts = cmdArgs defopts >>= processOpts >>= checkOpts

processOpts :: Opts -> IO Opts processOpts opts = do let opts' = decodeOpts opts fixMostOptDates opts'

| Convert possibly encoded option values to regular unicode strings. decodeOpts :: Opts -> Opts decodeOpts opts@Opts{..} = opts { file = maybe Nothing (Just . fromPlatformString) file ,begin = maybe Nothing (Just . fromPlatformString) begin ,end = maybe Nothing (Just . fromPlatformString) end ,period = maybe Nothing (Just . fromPlatformString) period ,display = maybe Nothing (Just . fromPlatformString) display ,format = maybe Nothing (Just . fromPlatformString) format ,alias = map fromPlatformString alias ,rules_file = maybe Nothing (Just . fromPlatformString) rules_file ,args_ = map fromPlatformString args_ }

| Convert any relative dates within these options, except for the period option, to fixed dates, based on today's date. Note this means the dates specified by a period expression can change if the date changes during a program run, whereas begin, end, display option dates are fixed at startup. fixMostOptDates :: Opts -> IO Opts fixMostOptDates opts@Opts{..} = do d <- getCurrentDay let fixbracketeddatestr "" = "" fixbracketeddatestr s = "[" + fixSmartDateStr d (init $ tail s) + "]" return $ opts { begin = maybe Nothing (Just . fixSmartDateStr d) begin ,end = maybe Nothing (Just . fixSmartDateStr d) end ,display = maybe Nothing (Just . regexReplaceBy "\\[.+?\\]" fixbracketeddatestr) display }

checkOpts :: Opts -> IO Opts checkOpts opts = do case formatFromOpts opts of Left err -> optsError err Right _ -> return () d <- getCurrentDay case maybe Nothing (Just . parsePeriodExpr d) $ period opts of Just (Left perr) -> optsError $ show perr _ -> return () when (null $ args_ opts) $ optsError "a command is required." return opts

optsError :: String -> IO () optsError s = putStrLn s >> exitWith (ExitFailure 1) optsError = ioError . userError' . (++ " Run with help to see usage.")

| Figure out the overall date span we should report on, based on any begin/end/period options provided and a reference date. If there is a period option, the others are ignored. dateSpanFromOpts :: Day -> Opts -> DateSpan dateSpanFromOpts d Opts{period=Just p} = case parsePeriodExpr d p of Right (_, span) -> span Left e -> error' $ "could not parse period option: "++show e dateSpanFromOpts d Opts{..} = DateSpan (maybeday begin) (maybeday end) where maybeday = maybe Nothing (Just . parsedate . fixSmartDateStr d)

| Figure out the reporting interval, if any, specified by the options. period overrides daily overrides weekly overrides monthly etc. intervalFromOpts :: Opts -> Interval intervalFromOpts Opts{period=Just p} = case parsePeriodExpr (parsedate "0001/01/01") p of Right (interval, _) -> interval Left e -> error' $ "could not parse period option: "++show e intervalFromOpts Opts{..} = if daily then Days 1 else if weekly then Weeks 1 else if monthly then Months 1 else if quarterly then Quarters 1 else if yearly then Years 1 else NoInterval

| Parse the format option if any, or raise an error if parsing fails. formatFromOpts :: Opts -> Either String [FormatString] formatFromOpts opts = maybe (Right defaultBalanceFormatString) parseFormatString $ format opts

| Default line format for balance report: "%20(total) %2(depth_spacer)%-(account)" defaultBalanceFormatString :: [FormatString] defaultBalanceFormatString = [ FormatField False (Just 20) Nothing Total , FormatLiteral " " , FormatField True (Just 2) Nothing DepthSpacer , FormatField True Nothing Nothing Format.Account ]

| Get the value of the baseurl option, if any. baseUrlFromOpts :: Opts -> Maybe String baseUrlFromOpts = const Nothing base_url

| Get the value of the (last) port option, if any. portFromOpts :: Opts -> Maybe Int portFromOpts = const Nothing port

| Get a maybe boolean representing the last cleared/uncleared option if any. clearedValueFromOpts :: Opts -> Maybe Bool clearedValueFromOpts Opts{..} | cleared_ = Just True

uncleared = Just False
otherwise = Nothing

| Detect which date we will report on, based on effective. whichDateFromOpts :: Opts -> WhichDate whichDateFromOpts opts = if effective opts then EffectiveDate else ActualDate

| Get the journal file path from options, an environment variable, or a default journalFilePathFromOpts :: Opts -> IO String journalFilePathFromOpts opts = do istimequery <- usingTimeProgramName f <- if istimequery then myTimelogPath else myJournalPath return $ fromMaybe f $ file opts

| Were we invoked as \"hours\" ? usingTimeProgramName :: IO Bool usingTimeProgramName = do progname <- getProgName return $ map toLower progname == progname_cli_time

aliasesFromOpts :: Opts -> [(AccountName,AccountName)] aliasesFromOpts = map parseAlias . alias where similar to ledgerAlias parseAlias :: String -> (AccountName,AccountName) parseAlias s = (accountNameWithoutPostingType $ strip orig ,accountNameWithoutPostingType $ strip alias') where (orig, alias) = break (='') s alias' = case alias of ('=':rest) -> rest _ -> orig

command :: Opts -> String command = headDef "" . args_

patterns :: Opts -> [String] patterns = tailDef [] . args_

| Gather filter pattern arguments into a list of account patterns and a list of description patterns. We interpret pattern arguments as follows: those prefixed with "desc:" are description patterns, all others are account patterns; also patterns prefixed with "not:" are negated. not: should come after desc: if both are used. parsePatternArgs :: [String] -> ([String],[String]) parsePatternArgs args = (as, ds') where descprefix = "desc:" (ds, as) = partition (descprefix `isPrefixOf`) args ds' = map (drop (length descprefix)) ds

| Convert application options to the library's generic filter specification. optsToFilterSpec :: Opts -> Day -> FilterSpec optsToFilterSpec opts d = FilterSpec { datespan=dateSpanFromOpts d opts ,cleared=clearedValueFromOpts opts ,real=real_ opts ,empty=empty_ opts ,acctpats=apats ,descpats=dpats ,depth = depth_ opts } where (apats,dpats) = parsePatternArgs $ patterns opts

tests_Hledger_Cli_Options = TestList [ "dateSpanFromOpts" ~: do let todaysdate = parsedate "2008/11/26" gives = is . show . dateSpanFromOpts todaysdate defopts `gives` "DateSpan Nothing Nothing" defopts{begin=Just "2008",end=Just "2009"} `gives` "DateSpan (Just 2008-01-01) (Just 2009-01-01)" defopts{period=Just "in 2008"} `gives` "DateSpan (Just 2008-01-01) (Just 2009-01-01)" defopts{begin=Just "2005",end=Just "2007",period=Just "in 2008"} `gives` "DateSpan (Just 2008-01-01) (Just 2009-01-01)"

,"intervalFromOpts" ~: do let gives = is . intervalFromOpts defopts `gives` NoInterval defopts{daily=True} `gives` Days 1 defopts{weekly=True} `gives` Weeks 1 defopts{monthly=True} `gives` Months 1 defopts{quarterly=True} `gives` Quarters 1 defopts{yearly=True} `gives` Years 1 defopts{period=Just "weekly"} `gives` Weeks 1 defopts{period=Just "monthly"} `gives` Months 1 defopts{period=Just "quarterly"} `gives` Quarters 1 defopts{weekly=True,period=Just "yearly"} `gives` Years 1

]

cmdargs implicit ADT with extra options map

data Opts = Opts { :: hledger options file :: Maybe FilePath :: hledger-lib options ,begin :: Maybe String ,end :: Maybe String ,period :: Maybe String ,cleared_ :: Bool ,uncleared :: Bool ,cost :: Bool ,depth_ :: Maybe Int ,display :: Maybe String ,effective :: Bool ,empty_ :: Bool ,no_elide :: Bool ,real_ :: Bool ,flat :: Bool ,drop_ :: Int ,no_total :: Bool ,daily :: Bool ,weekly :: Bool ,monthly :: Bool ,quarterly :: Bool ,yearly :: Bool ,format :: Maybe String :: hledger options ,alias :: [String] ,no_new_accounts :: Bool ,rules_file :: Maybe String ,binary_filename :: Bool ,debug :: Bool

:: add-ons' extra options ,extra_opts :: M.Map String String #ifdef HLEDGERVTY ,debug_vty :: Bool #endif #ifdef HLEDGERWEB ,base_url :: Maybe String ,port :: Maybe Int #endif #ifdef HLEDGERCHART ,chart_output :: Maybe String ,chart_items :: Maybe Int ,chart_size :: Maybe String #endif

,args_ :: [String]

} deriving (Show, Data, Typeable)

cmdargs explicit string map

progname = Hledger.Cli.progname ++ "-vty" progversion = progversionstr progname

usage_preamble = "Usage: hledger-vty [OPTIONS] [PATTERNS]\n" + "\n" + "Reads your ~/.hledger.journal file, or another specified by $LEDGER_FILE or -f, and\n" + "starts the full-window curses ui.\n" + "\n"

type Opts = Map String String

vtymode :: Mode Opts vtymode = mode "hledger-vty options" M.empty "general and hledger-vty-specific options" vtyargs vtyflags vtyargs = flagArg (\v opts -> Right $ M.insert "dummy args flag" v opts) "DUMMY" vtyflags = [ flagReq ["begin","b"] (\v opts -> Right $ M.insert "begin" v opts) "DATE" "report on transactions on or after this date" ,flagReq ["end","e"] (\v opts -> Right $ M.insert "end" v opts) "DATE" "report on transactions before this date" ,flagReq ["period","p"] (\v opts -> Right $ M.insert "period" v opts) "PERIODEXPR" "report on transactions during the specified period and/or with the specified reporting interval" ,flagHelpSimple id ]

defopts = Hledger.Cli.defopts { debug_vty = def &= help "run with no terminal output, showing console" ,args_ = def &= args &= typ "PATTERNS" } &= program progname &= summary progversion

getHledgerVtyOpts :: IO Opts getHledgerVtyOpts = processArgs vtymode >>= processOpts >>= checkOpts

processOpts :: Opts -> IO Opts processOpts = Hledger.Cli.processOpts

checkOpts :: Opts -> IO Opts checkOpts = Hledger.Cli.checkOpts

main :: IO () main = do opts <- getHledgerVtyOpts when ("debug" `M.member` opts) $ printf "%s\n" progversion >> printf "opts: %s\n" (show opts) return () runWith opts

runWith :: Opts -> IO () runWith opts

"binary_filename" `M.member` opts = putStrLn (binaryfilename progname_cli)
otherwise = withJournalDo opts vty
cmdargs very explicit string map

import Control.Monad import Data.Map as M import Data.Time.Calendar import System.Console.CmdArgs import System.Console.CmdArgs.Explicit import System.Console.CmdArgs.Text

in_ = M.member

type Opts = Map String String

vtyargs = flagArg (\v opts -> Right $ M.insert "PATTERNS" v opts) "query patterns"

vtyflags = [ flagNone ["debug-vty"] (\opts -> M.insert "debug-vty" "" opts) "run with no terminal output, showing console" ]

commonflags = [ flagReq ["begin","b"] (\v opts -> Right $ M.insert "begin" v opts) "DATE" "report on transactions on or after this date" ,flagReq ["end","e"] (\v opts -> Right $ M.insert "end" v opts) "DATE" "report on transactions before this date" ,flagReq ["period","p"] (\v opts -> Right $ M.insert "period" v opts) "PERIODEXPR" "report on transactions during the specified period and/or with the specified reporting interval" ,flagHelpSimple (M.insert "help" "") ,flagVersion (M.insert "version" "") ]

vtymode :: Mode Opts vtymode = Mode { modeGroupModes = toGroup [] ,modeNames = ["vty"] ,modeValue = M.empty ,modeCheck = Right ,modeReform = const Nothing ,modeHelp = "" ,modeHelpSuffix = [] ,modeArgs = ([], Nothing) ,modeGroupFlags = Group { groupUnnamed = [] ,groupHidden = [] ,groupNamed = [ ("vty options", vtyflags) ,("general options", commonflags) ] } }

main = do opts <- processArgs vtymode print opts when ("help" `in_` opts) $ putStr $ showText defaultWrap $ helpText HelpFormatDefault vtymode

optsToFilterSpec :: Opts -> Day -> FilterSpec optsToFilterSpec opts d = FilterSpec { datespan=nulldatespan dateSpanFromOpts d opts ,cleared=clearedValueFromOpts opts ,real="real" `in_` opts ,empty="empty" `in_` opts ,acctpats=[] apats ,descpats=[] dpats ,depth = maybe Nothing (Just . read) $ M.lookup "depth" opts } where (apats,dpats) = parsePatternArgs $ patterns opts

clearedValueFromOpts opts | "cleared" `in_` opts = Just True

"uncleared" `in_` opts = Just False
otherwise = Nothing

dateSpanFromOpts :: Day -> Opts -> DateSpan dateSpanFromOpts d Opts{period=Just p} = case parsePeriodExpr d p of Right (_, span) -> span Left e -> error' $ "could not parse period option: "++show e dateSpanFromOpts d Opts{..} = DateSpan (maybeday begin) (maybeday end) where maybeday = maybe Nothing (Just . parsedate . fixSmartDateStr d)

cmdargs explicit string map -> separate ADTs

1. option values for use in this and maybe other packages. These are the data we want to collect.

report options, used in hledger-lib and above data ReportOpts = ReportOpts { begin_ :: Maybe Day ,end_ :: Maybe Day ,period_ :: Maybe (DateSpan,Interval) ,cleared_ :: Bool ,uncleared_ :: Bool ,cost_ :: Bool ,depth_ :: Maybe Int ,display_ :: Maybe String ,effective_ :: Bool ,empty_ :: Bool ,no_elide_ :: Bool ,real_ :: Bool ,flat_ :: Bool ,drop_ :: Int ,no_total_ :: Bool ,daily_ :: Bool ,weekly_ :: Bool ,monthly_ :: Bool ,quarterly_ :: Bool ,yearly_ :: Bool ,format_ :: Maybe String } deriving (Show) , Data, Typeable)

defreportopts = ReportOpts def def def def def def def def def def def def def def def def def def def def def

instance Default ReportOpts where def = defreportopts

cli options, used in hledger and above data CliOpts = CliOpts { file_ :: Maybe FilePath ,alias_ :: [String] ,binary_filename :: Bool ,debug_ :: Bool ,command_ :: String ,patterns_ :: [String] add ,no_new_accounts_ :: Bool convert ,rules_file_ :: Maybe FilePath

,reportopts_ :: ReportOpts } deriving (Show) , Data, Typeable)

defcliopts = CliOpts def def def def def def def def def

instance Default CliOpts where def = defcliopts

2. reusable/extensible command-line flags, help and initial parsing for the above.

generalflags = [ flagReq ["file","f"] (\v opts -> Right $ M.insert "file" "" opts) "FILE" "use a different journal file; - means stdin" ,flagHelpSimple (M.insert "help" "") ,flagVersion (M.insert "version" "") ,flagReq ["begin","b"] (\v opts -> Right $ M.insert "begin" v opts) "DATE" "report on transactions on or after this date" ,flagReq ["end","e"] (\v opts -> Right $ M.insert "end" v opts) "DATE" "report on transactions before this date" ,flagReq ["period","p"] (\v opts -> Right $ M.insert "period" v opts) "PERIODEXPR" "report on transactions during the specified period and/or with the specified reporting interval" ]

addflags = [ flagNone ["no-new-accounts"] (\opts -> M.insert "no-new-accounts" "" opts) "" ]

convertflags = [ flagReq ["rules-file"] (\v opts -> Right $ M.insert "rules-file" v opts) "FILE" "" ]

cliargs = flagArg (\v opts -> Right $ M.insert "args" v opts) "COMMAND [PATTERNS]"

type RawOpts = Map String String

progmode :: Mode RawOpts progmode = Mode { modeGroupModes = toGroup [] ,modeNames = ["hledger"] ,modeValue = M.empty ,modeCheck = Right ,modeReform = const Nothing ,modeHelp = "hledger options test" ,modeHelpSuffix = [] ,modeArgs = ([], Just cliargs) ,modeGroupFlags = Group { groupUnnamed = [] ,groupHidden = [] ,groupNamed = [("general options", generalflags) ,("add options", addflags) ,("convert options", convertflags) ] } }

3. post-processing, additional checking and conversion of raw options data Opts = Opts { cliopts :: CliOpts, reportopts :: ReportOpts } deriving (Show) defopts = Opts defcliopts defreportopts

processOpts :: RawOpts -> CliOpts processOpts rawopts = defcliopts { file_ = stringopt "file" rawopts ,debug_ = boolopt "debug" rawopts ,reportopts_ = defreportopts { begin_ = maybedateopt "begin" rawopts ,end_ = maybedateopt "end" rawopts } } where optserror = error' boolopt = M.member stringopt = M.lookup maybedateopt name rawopts = case M.lookup name rawopts of Nothing -> Nothing Just s -> Just $ fromMaybe (optserror $ "could not parse "name" date: "++s) $ parsedateM s

testmain = do rawopts <- processArgs progmode print rawopts print $ processOpts rawopts when ("help" `in_` rawopts) $ putStr $ showText defaultWrap $ helpText HelpFormatDefault progmode

other things to try:

http://www.haskell.org/haskellwiki/Heterogenous_collections make cmdargs generate flat help from nested ADTs make cmdargs pass through extra opts without

still to do

move *FromOpts into toOpts

2012

2012/5/5 release prep

5/14 finish parsing, tests changes

5/15 matcher -> query, cleanup

5/16 tests, using query consistently

5/17 tests, porting entriesReport to query

5/29 release

6/29 release

6/30 announce, notes

7/1 notes

old/dev/project backlog - see also trello

docs

documentation advice

As someone who is struggling to learn Snap (and I am trying to use it in one project), I would appreciate some nice explanations of the basic concepts, minimal and isolated from each other. For example, just a static template, just having a few constant splices of different types and showing them in a template.

In general, examples are most useful when they explain some abstract rule. What are the rules these examples explain? Just phrase it in one sentence for people to remember. Like, "Templates can be composed of other templates using the apply tag", then show the example of how to do it. Also, adding "negative" examples, which show common errors, is a good approach.

errors

balance: drop without flat should only drop top-level account name parts (or be ignored)

balance (flow): show today as end date instead of end of current period ?

cli: -p 'weekly in jan-feb' confusion, it should mean the same as 'weekly from jan to feb'

cli: aliasing an account prefix to '' should work

cli: don't list directories as addons (hledger-chart)

csv: rule not setting comment

if EDISON GAS COMPANY account2 expenses:personal:home:utilities comment household:

add: default amount adds one decimal place when journal contains no decimals 2d

add: excessive precision in default balancing amount 1d

shelltest tests/add.test -t10
find original justification or drop

add: learn decimal point/thousands separator from the journal and/or add session ? 2d

Eg: comma is already used as thousands separator in the journal, but add interprets it as decimal point giving a wrong default for amount 2 (though the correct journal transaction is written in this case)

$ hledger -f t add Adding transactions to journal file "t". To complete a transaction, enter . (period) at an account prompt. To stop adding transactions, enter . at a date prompt, or control-d/control-c. date, or . to end [2011/09/30]: description []: z account 1: a amount 1: 1,000 account 2: b amount 2 [-1,0]: account 3, or . to record: . date, or . to end [2011/09/30]: . $ cat t ; journal created 2011-09-30 by hledger

2011/09/30 a $1,000,000.00 b

2011/09/30 x a $1,2 b

2011/09/30 y a $1.2 b

2011/09/30 z a 1,000 b

web: unknown flag port

$ hledger web port=5001 -f all.journal paypal hledger: Unknown flag: port

print: virtual posting parentheses throwing off layout

bal: should flat show inclusive or exclusive balances ??

double quote matches everything ? 1

web: stray bracket in journal edit form title 1

web: enter doesn't work in add form completing fields 1d

research this dhtmlxcombo issue

parsing: assertion claiming wrong actual balance

expected balance is $136.03, actual balance was $-1163.97.

parsing: balance assertion doesn't work without a commodity symbol (takes = as one)

parsing: is = B an assertion or an assignment ?

parsing: decimal point/thousands separator confusion ? 1d

<<< 2011/09/30 a $1,000,000.00 b

2011/09/30 x a $1,2 b

2011/09/30 y a $1.2 b >>> hledger -f t print 2011/09/30 a $1,000,000.00 b $-1,000,000.00

2011/09/30 x a $1.20 b $-1.20

2011/09/30 y a $1.20 b $-1.20

parsing: recursive file includes cause a hang 2

echo "!include rec" > rec hledger -f rec print

parsing: "could not balance" error does not show line number 1d

parsing: extra noise with eg bad date parse errors 1d

$ cat t.journal 200/1/99 x a 1 b $ ./hledger.hs -f t.journal print hledger.hs: could not parse journal data in t.journal "t.journal" (line 1, column 9): unexpected " " <- undesired expecting digit <- noise bad year number: 200

parsing: confusing error when journal lacks a final newline 1d

$ cat - >t.j 2010/1/2 a 1 b<ctrl-d> $ hledger -f t.j bal hledger: could not parse journal data in t.j "t.j" (line 3, column 3): unexpected "b" expecting comment or new-line

convert: 49 convert should report rules file parse errors better 1d

not: does not work with date: etc.

journalAddFile is called in reverse order of includes 2

irr: flags require preceding

cli: not always possible to pass flags to subcommand

documentation

hledger intro tutorial:

what problem did ledger solve when I started ?
I needed to track my time at work
I needed a transparent, open, future-proof data format
I needed simple, reliable, fixable software with no lock-in
I wanted an accounting tool without distracting/frustrating unfixable usability/functionality bugs
what problem did hledger solve when I started ?
I neededed a better implementation of ledger (for me; that meant ie more installable, intuitive, documented, bug free, easy to hack on and extend)
I needed to make consistent bookkeeping more fun and motivating
I wanted a not-too-demanding learn haskell project
I wanted reusable accounting libraries available in haskell for experiments
I wanted to explore making an easy accounting app I could sell
what problem is hledger solving now ?
same as above ?

doc: manual rewrites

developer notes

2012/7 cleanup
quick cleanup finance onwards 2
add some estimates 1
review/prune backlog 1
estimate summing 1d
research existing, ask in #orgmode
org-sum
burndown charts 2d
research existing tools

finalise/link 2012 survey 2

document status flag better 1

review/prune docs 1d

announcements

list
release
HCAR, twice yearly
update entry & process

short description

collect/clarify

hledger is a robust command-line accounting tool with a simple plain text data format.

hledger is a reporting tool for accounting transactions stored in a simple human-editable text format.

hledger is a computer program for easily tracking money, time, or other transactions, usually recorded in a general journal file with a simple human-editable markup format.

hledger is primarily a reporting tool, but it can also help you add transactions to the journal, or convert from other data formats.

hledger is a haskell port and friendly fork of John Wiegley's c++ ledger tool.

hledger aims to be a reliable, practical, useful tool for (slightly geeky) users and a reusable library for haskell programmers interested in finance.

hledger is quite simple in essence, aiming to be a reliable low-level parsing-and-reporting tool that doesn't get in your way.

For some, it is a less complex, less expensive, more efficient alternative to Quicken or Quickbooks.

hledger is available for free under the GNU General Public License.

hledger reads plain text files (general journal, timelog, or CSV format) describing transactions (in money, time or other commodities) and prints the chart of accounts, account balances, or transactions you're interested in.

hledger is a free program that helps you understand your finances, making calculations based on data stored a simple text file. If you prefer the command line and a text editor to a big gui application, hledger gives you the power of Quicken and Quickbooks without the complexity.

Your financial data will outlive your financial software, so it should have longevity and accessibility. Its integrity is important to your peace of mind, so changes should be transparent and (if desired) version controlled. It may also be important to allow multiple authors to edit safely. A structured, easy-to-parse, human-friendly plain text format, as in the wiki world, provides a good balance of longevity, reliability, transparency and flexibility.

hledger helps you track and understand your finances, making calculations based on data stored in a simple text file. If you prefer the command line and a text editor to a big gui application, hledger gives you the power of Quicken and Quickbooks without the complexity.

Features: reads transactions in journal, timelog, or CSV format; handles multi-currency/multi-commodity transactions; prints the chart of accounts, account balances, or transactions you're interested in, quickly; scriptable.

hledger is written in the Haskell programming language; it demonstrates a pure functional implementation of ledger.

medium intro blurb

collect/clarify
README file
hledger.hs module description
hledger.cabal description field (exclude home page link)
home page description (http://joyful.com/Hledger/editform)
mail list description (http://groups.google.com/groups/hledger -> edit welcome msg)
gmane description
darcsweb description
keep in sync
refine process

command-line docs

keep usage info in sync
Options.hs
MANUAL.md
browse/search manual content 2d

feature list

full 1
short 1

manual

fix pre/toc overlap on manual 2
clarify reference nature 1

FAQ

create/highlight 1d
life cycle of top-level accounts

For personal ledgers, when you're born, all accounts are at zero (one hopes) and as you live:

  1. Equity accounts accommodate your previous years of not maintaining accounts (fixed, probably negative)
  2. Expense accounts become more and more positive (unavoidably)
  3. Income accounts become more and more negative (on payday)
  4. Assets Accounts become more and more positive (in good times)
  5. Liability account become more positive (in good times, when you pay them off) and more negative (when you use them to buy things).

When you die, Equity: and Income: will stand at large negative balances, Expense: and Assets: will stand at large positive balances and Liabilities will have to be paid (out of Assets) before your heirs get what's left.

adapted from Ben Alexander, ledger-cli

website

review stats 1h
clean up stats 1d
refresh

progressive tutorial

plan, begin 1d

screencasts

brainstorm
intro
intro to hledger
place in the world
basic installation
quick demo
where to go from here
installing hledger on windows
installing hledger on mac
installing hledger on unix
accessing hledger's support forums
website
mail list
irc channel
reporting a hledger bug
using
income/expense tracking
time tracking
downloading bank data
reconciling with bank statement
see time reports by day/week/month/project
get accurate numbers for client billing and tax returns
find unpaid invoices
developing
intro to hledger development
testing hleder
documenting hledger
a hledger coding example
a tour of hledger's code
ledger cooperation

blog posts

examples/how-tos

hledger/ledger comparison/feature matrix 1d

improve aesthetics

embed screenshots in web docs
use highslide

improve liveness

show feeds on site ?
commits
cc/summarise repo activity to list ?

developer guide

clarify/merge developer guide 2h
How to do anything that needs doing in the hledger project.
website & documentation
overview of hledger docs
how the site is built
convenience urls

list.hledger.org - mail list bugs.hledger.org - issue tracker bugs.hledger.org/1 - go to specific issue bugs.hledger.org/new - create a new issue hledger.org/{list,bugs}/* also works

issue tracking
testing

hledger's unit tests and a simple test runner are built in. They can be run several ways:

$ hledger test [PAT] $ make unittest $ make autotest

They can also be built as a separate executable, in case needed for cabal test. (?) This requires test-framework, which may not work on windows.

$ make unittest-standalone

hledger's functional tests are a set of @shelltestrunner@ tests defined by .test files in the tests\/ subdirectory.

$ make functest

Shell tests can also be defined as doctests, literal blocks embedded in modules' haddock docs, though this is hardly used. For example:

@ $ bin\/hledger -f data\/sample.journal balance o $1 expenses:food $-2 income $-1 gifts $-1 salary


$-1 @

$ make doctest

coding
funding process
donation blurb

If you like <a href="http://joyful.com/repos/project">project</a> or have benefited from it, you can give back by making one-time or periodic donations of any amount. This also allows me to offer further enhancements, maintenance and support for this project. Thanks!

reference
unsafe things which may fail at runtime include..
incomplete pattern matching
error
printf
read

api docs

darcs show authors

clean up output 2
trygve
encoding

eg in text-mode emacs 24

roadmap

review old
1.0

culmination of 0.x releases - stable/usable/documented followup releases are 1.01, 1.02.. GHC 6.12/HP 2010 primary platform GHC 6.10/HP 2009 also supported if possible GHC 6.8 might work for core features, but not officially supported separate ledger package ? license ? separate vty, web packages ? support plugins ? web: loli+hsp+hack+simpleserver/happstack, or yesod+hstringtemplate+wai+simpleserver/happstack ? add: completion ? chart: register charts ? histogram: cleaned up/removed complete user manual binaries for all platforms ?

2.0

development releases are.. 1.60, 1.61.. or 1.98.01, 1.98.02.. separate ledger lib plugins Decimal binaries for all platforms

internal code docs

live demos/talks

finance

develop funding process

donate button, see chimoo guy
funding document 2009/01
text

===== funding =====

vision ====

How to grow the hledger project ?

I'm looking for ways to fund active and sustainable hledger development by me and others.

A secondary goal is to develop new sustainable models and processes for funding free software developers and other community projects.

This is sometimes the point in a free sw project's development where the project leader seemingly loses the plot, alienates contributors and destroys the community's good-will dynamic. I've seen it many times, but a few have succeeded and I want to be one of them - so that I can eat, have a modicum of stability and do my best work in service to the community. At worst, I'll look bad but the project will still be out there. At best I'll live more easily and joyfully while serving the cause of Financial Solvency!

So I'm beginning by posting these notes and inviting your thoughts - as much or as little as folks want to give. How could we do this so that all benefit ?

funding models ============ Brainstorming some possible funding models & processes.

  • grants

    How to find possible grant sources ?

    • con

      • getting grant funding is a whole new field to study
      • slow and time intensive, I imagine
  • donations

    Solicit donations.

    • pro

      • simple
    • con

      • often difficult
      • donators do not feel a direct benefit
  • shareware

    Release the project under a non-free license, requiring commercial users to pay the fee on an honour basis (eg).

    • pro

      • flexible, low administration, encourages trust
    • con

      • effectively closed-source ? would inhibit collaboration
      • benefit is still indirect, only a proportion will pay
      • enforcement/guilt may come into play
  • limited-time premium branch

    The funded version of hledger gets some desirable premium features before the free version and is closed-source. Funders/customers pay a fixed price for immediate access to the funded version. Yearly, a new funded version is released and the old funded version is merged into the free version. (To gain experience it could be done on a smaller scale, eg monthly/quarterly.)

    • pro

      • all features reach community, predictably
      • customers are also community funders
      • customers receive direct benefit from paying
    • con

      • free sw developers compete/outshine the premium branch
  • bounties

    Some (or all) feature, bugfix, project management or other tasks are published with a bounty attached. When the bounty is paid by one or more funders, the task is performed and delivered. Or, bounty is paid on completion of task (honour system).

    • pro

      • funders receive direct benefit
  • bounties using fundable.org (eg)

    A more organised form of the above, perhaps facilitating trust, co-funding and larger bounties.

    • pro

      • proven process developed by others
    • con

      • fundable takes a cut
  • hosted service

    Offer hosted and managed ledgers, perhaps with premium features, for a monthly fee

    • pro

      • proven model
      • clear benefit to customers, especially non-technies
    • con

      • success of free/self-installed version competes with hosting service
      • some will avoid web-hosting their financial data
  • customisation Offer per-user customisations, possibly to be merged in the trunk, for a fee
  • support Offer user/developer support for a fee
  • training Offer application and/or financial training for a fee
  • profit sharing/tithing Each period (quarter, half-year, year), donate 10% (eg) to project contributors and/or supporting projects
  • transparent funding Funding and usage of funds is published on the web as a ledger
  • opaque funding All funding and spending need not be made public

strengths ======= hledger has some aptitudes in this area:

  • hledger deals with money => hledger users will tend to have some money
  • hledger's purpose is to increase financial success => users will feel its value to their bottom line
  • hledger is a tool that can support project funding, eg by publishing community funding data

weaknesses ========

  • hledger doesn't have a nice ui yet
  • hledger has a limited featureset
  • hledger requires work, eg data entry and chart of accounts maintenance
  • hledger is geeky
  • there is competition
  • hledger has no compelling market niche (aside from payment-averse free software users)

competitors/fellow niche inhabitants ==================================

  • web apps

    • netsuite
    • sql-ledger, ledgersmb
    • wesabe
  • desktop apps

    • quickbooks
    • quicken
    • ms money
    • grisbi
    • gnucash
    • excel
    • ledger!
responses
albino

have you considered talking to business who hate their financial sw and going from there

gwern

most haskellers have never heard of hledger, sounds arrogant or hubristic to talk of charging for it

license change ?
home edition
real-time project ledger
in-place transaction editing fund drive

Fund drive: hledger-web in-place transaction editing

Goal: I would like to raise $X or more to fund basic in-place transaction editing for hledger-web. hledger-web is a web-based GUI for hledger (and ledger), which are free/open-source accounting programs providing a lean and efficient alternative to quicken, gnucash, mint.com etc.

Current hledger-web[1] has simple web forms for adding transactions and for editing the whole journal, but there is no easy ui for editing a single existing transaction. Such a ui is an important step towards making hledger (and ledger) usable by non techies, which would greatly expand these tools' applicability and potential user/contributor base.

Plan: do the front-end javascript and backend haskell work required to support:

  • click date, description, account or amount cells in a register view to make that cell editable
  • tab moves to the next cell
  • enter or click on save button updates the transaction in the journal, overwriting/rewriting the whole file
  • tested in firefox/chrome/safari

The proposed amount will fund about 10 hours of work, so the above features must be implemented very expeditiously. Other improvements will be tackled in a followup fund drive if this one succeeds (or in this one if the funding goal is exceeded.) Those future items include:

  • history/content awareness, smart defaults and auto-completion wherever useful
  • date picker widget
  • ability to add/remove postings
  • ability to edit metadata/tags
  • ability to edit other transaction/posting fields
  • ledger compatibility
  • compatibility testing/fixes for all the major browsers
  • edit conflict checking - don't overwrite concurrent external edits
  • try harder to preserve existing file layout/co-exist better with external edits
  • a similar ui for adding new transactions
  • pleasant visual style

Also, 10% of the amount raised will be tithed to three contributing projects or developers (ledger and two others of my choice.)

This project will go forward if

[1] http://demo.hledger.org:5001

testing

test running improvements

test: duplicate runs

$ hledger test 'showTransaction$' Cases: 6 Tried: 0 Errors: 0 Failures: 0([],"") ### Failure in: 0:showTransaction show a balanced transaction, eliding last amount expected: "2007/01/28 coopportunity\n expenses:food:groceries $47.18\n assets:checking\n\n" but got: "2007/01/28 coopportunity\n expenses:food:groceries $47.18\n\n assets:checking \n\n\n" Cases: 6 Tried: 1 Errors: 0 Failures: 1([],"") ### Failure in: 1:showTransaction show a balanced transaction, no eliding expected: "2007/01/28 coopportunity\n expenses:food:groceries $47.18\n assets:checking $-47.18\n\n" but got: "2007/01/28 coopportunity\n expenses:food:groceries $47.18\n\n assets:checking $-47.18\n([],"") \n\n" Cases: 6 Tried: 2 Errors: 0 Failures: 2([],"") ### Failure in: 2:showTransaction show an unbalanced transaction, should not elide expected: "2007/01/28 coopportunity\n expenses:food:groceries $47.18\n assets:checking $-47.19\n\n" but got: "2007/01/28 coopportunity\n expenses:food:groceries $47.18\n\n assets:checking $-47.19\n([],"") \n\n" Cases: 6 Tried: 3 Errors: 0 Failures: 3([],"") ### Failure in: 3:showTransaction show an unbalanced transaction with one posting, should not elide expected: "2007/01/28 coopportunity\n expenses:food:groceries $47.18\n\n" but got: "2007/01/28 coopportunity\n expenses:food:groceries $47.18\n\n\n" Cases: 6 Tried: 4 Errors: 0 Failures: 4([],"") ### Failure in: 4:showTransaction show a transaction with one posting and a missing amount expected: "2007/01/28 coopportunity\n expenses:food:groceries \n\n" but got: "2007/01/28 coopportunity\n expenses:food:groceries \n\n\n" Cases: 6 Tried: 5 Errors: 0 Failures: 5([],"") ### Failure in: 5:showTransaction show a transaction with a priced commodityless amount expected: "2010/01/01 x\n a 1 @ $2\n b \n\n" but got: "2010/01/01 x\n a 1 @ $2\n\n b \n([],"") \n\n" Cases: 6 Tried: 6 Errors: 0 Failures: 6 1

stop on first failure
run tests in bottom up order

envision better test setup

every parser has a test and is easy to test
easy to run any single test or module's tests
tests run bottom up by default
test runner can select tests precisely eg by regexp
test runner stops at first failure by default

documentation

site up, current ?
demo up, current ?
haddock building, current ?
doctests ?

unit

hunit
quickcheck
easier unit test development

functional

ledger file parsing tests
test all ledger file format features
clarify hledgerisms in file format - that hledger can read but ledger can't
ledger 3 baseline tests
MaybeSo subtotal rounding issue

I had a question about balance totals. Given this test data:

$ cat test.dat D $1,000.00 P 2011-01-01 22:00:00-0800 TESTA $78.35 P 2011-01-01 22:00:00-0800 TESTB $15.86 P 2011-01-01 22:00:09-0800 TESTC $13.01

2011/01/01 Example Assets:Brokerage:TESTA 188.424 TESTA @ $76.61 Assets:Brokerage:TESTB 1,809.282 TESTB @ $15.60 Assets:Brokerage:TESTC 384.320 TESTC @@ $5,000.00 Assets:Brokerage:TESTC 5.306 TESTC @@ $68.18 Equity:Opening Balances

I'm a little bit surprised that the sub-accounts reflect a difference from the top level account w/re to rounding the last cent:

$ ledger -V -f test.dat bal $48,527.27 Assets:Brokerage $14,763.02 TESTA $28,695.21 TESTB $5,069.03 TESTC $-47,728.14 Equity:Opening Balances


$799.13

Even if no-rounding is passed in:

$ ledger -V -f test.dat --no-rounding bal $48,527.27 Assets:Brokerage $14,763.02 TESTA $28,695.21 TESTB $5,069.03 TESTC $-47,728.14 Equity:Opening Balances


$799.13

Is there something off with how the data aboce is set up? Should I be using be more place holders?

build & packaging

use -Wall and anything else useful
build with multiple ghc versions
cabal test
hackage upload
cabal install with:
ghc 6.8
ghc 6.10.x
windows
linux
macos
no flags
happs flag
vty flag

field

talkback, auto bug reports
usability
download & usage stats

packaging, installability

linux

debian/ubuntu packaging

mac

easy installer
easy startup

windows

easy installer
easy startup

refactoring

clarify need for & usage of primary/secondary/transaction/posting dates

makefile cleanups

make shell tests version independent

tests/no-such-file.test: rm -f $$; bin/hledger register -f $$; rm -f $$ tests/no-such-file.test: rm -f $$; bin/hledger balance no-total -f $$; rm -f $$ tests/add.test: rm -f t$$.j; bin/hledger -f t$$.j add; rm -f t$$.j tests/add.test: rm -f t$$.j; bin/hledger -f t$$.j add; rm -f t$$.j tests/add.test: rm -f t$$.j; bin/hledger -f t$$.j add; rm -f t$$.j tests/add.test: printf 'D $1000.00\n' >t$$.j; bin/hledger -f t$$.j add >/dev/null; cat t$$.j; rm -f t$$.j tests/add.test: printf 'D $1000.0\n' >t$$.j; bin/hledger -f t$$.j add >/dev/null; cat t$$.j; rm -f t$$.j tests/add.test: printf '2010/1/1\n a $1000.00\n b\n' >t$$.j; bin/hledger -f t$$.j add >/dev/null; cat t$$.j; rm -f t$$.j tests/add.test: printf '2010/1/1\n a $1000.0\n b\n' >t$$.j; bin/hledger -f t$$.j add >/dev/null; cat t$$.j; rm -f t$$.j tests/add.test: printf 'D $1000.0\nD £1,000.00\n' >t$$.j; bin/hledger -f t$$.j add; cat t$$.j; rm -f t$$.j tests/add.test:rm -f nosuch.journal; bin/hledger -f nosuch.journal add; rm -f nosuch.journal tests/add.test:## printf '\n\na\n1\nb\n' | bin/hledger -f /dev/null add tests/add.test:# bin/hledger -f /dev/null add tests/amount-layout-vertical.test:# bin/hledger -f - print tests/amount-layout-vertical.test:# bin/hledger -f - register tests/amount-layout-vertical.test:# bin/hledger -f - balance tests/parse-posting-error-pos.test:# bin/hledger -f- stat tests/null-accountname-component.test:# bin/hledger -f - balance -E tests/include.test: 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 ; bin/hledger -f a.journal print; rm -rf a.journal b tests/timelog-stack-overflow.test:#bin/hledger -f - balance tests/precision.test:# bin/hledger -f - print --cost tests/precision.test: bin/hledger -f - balance --cost tests/timezone.test:# bin/hledger -f - balance --no-total --cost tests/read-csv.test:rm -rf t.rules$$; printf 'date-field 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' | bin/hledger -f- print --rules-file t.rules$$; rm -rf t.rules$$ tests/read-csv.test: printf 'base-account Assets:MyAccount\ndate-field 0\ndate-format %%d/%%Y/%%m\ndescription-field 1\nin-field 2\nout-field 3\ncurrency $\n' >$$.rules ; bin/hledger -f- print --rules-file $$.rules; rm -rf $$.rules tests/read-csv.test:# 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' | bin/hledger convert --rules-file t.rules$$; rm -rf t.rules$$ tests/prices.test:# bin/hledger -f - print

abstract DataSource

review/simplify apis

simplify option types

more modularity

packages/namespace
hledger-datasource?
plugin strategy
export lists
graph and reduce dependencies

switch to http-conduit

include latest jquery, jquery-url, minified and non

clarify levels of abstraction

web ui balance sheet view - data model, view layout
hledger web framework - define routes, handlers/views/actions/controllers/presenters, skins/styles..
happstack - ? happstack api..
hledger app platform - hledger.hs, Options, Utils, withLedgerDo..
hledger lib - Ledger, TimeLog, Account, Transaction, Commodity..
hledger dev platform - make build, ci, test, bench, prof, check, release..
general libs - directory, parsec, regex-*, HUnit, time..
cabal - hledger.cabal, hackage..
ghc - ghc 6.8, 6.10..
haskell 98
unix/windows/mac platform

perf tuning experiments

string -> text
strict data fields
more profiling
faster parsec alternative

web: code/ui review/refactor

convert all to HTF ?

plugin architecture/modular packaging

goals
allow separately-packaged functionality to be discovered at run-time and integrated within the hledger ui.

Example: user installs hledger-ofx package from hackage, or adds Ofx.hs to their ~/.hledger/plugins/; then "ofx" is among the commands listed by hledger help, and/or is a new command available in the web and vty interfaces, and/or is a new file format understood by the convert command.

issues to consider
what is the api for plugins ?

they'll want to import Ledger lib, to work with ledger data structures

there are different kinds of "plugin". What could plugins provide ?
commands - for all uis, or for one or more of them (cli, web, vty..). A command may itself be a new ui.
import/export formats
skins/styles/templates for uis, eg the web ui ?
techniques to consider
running executables provided by plugins

a cli command plugin: cli execs the executable with same arguments a web command plugin: web ui runs the executable as a subprocess and captures the output

linking plugins into main app with direct-plugins

simplification of plugins lib main app needs to know the types used in plugin's interface weakens type safety, avoiding runtime errors requires extra care requires whole-program linking at plugin load time plugins can be discovered by querying ghc for installed packages or modules in a known part of the hierarchy maintained and keen to help

linking plugins into main app with plugins (original)

more complex than above more type-safe/featureful ?

doing whatever xmonad does with dyre
interpreting plugins under control of main app with hint

ghci in an IO-like monad types need converting, etc. plugins may run more slowly plugins can be discovered/loaded by module path or by loading files directly

Archive   ARCHIVE

DONE 98: build issue due to missing template files

CLOSED: [2013-01-24 Thu 09:30]

test fix
publish patch
close issue
improve clean build testing, possibly with cabal-dev

DONE fix/explain build errors

CLOSED: [2013-02-08 Fri 09:05]

DONE add: djp/sm enhancements

CLOSED: [2013-03-15 Fri 18:35]

DONE cleanups

CLOSED: [2013-02-25 Mon 16:38]

DONE 52 show added txn

CLOSED: [2013-02-25 Mon 16:38]

DONE 47 do-over

CLOSED: [2013-02-25 Mon 16:38]

DONE 96 command-line defaults

CLOSED: [2013-02-25 Mon 16:38]

DONE 45 allow comment/tag entry

CLOSED: [2013-02-25 Mon 16:38]

allow DESC ;COMMENT
allow AMOUNT ;COMMENT
DONE allow DATE CODE

CLOSED: [2013-02-25 Mon 16:38]

DONE confirm before adding txn

CLOSED: [2013-02-25 Mon 16:39]

DONE don't die after data entry if a partial date was provided on command line

CLOSED: [2013-03-04 Mon 18:09]

DONE decide how to wind this up, complete/announce bounty

CLOSED: [2013-03-09 Sat 07:57]

DONE add to release notes

CLOSED: [2013-03-09 Sat 07:57]

DONE update release notes

CLOSED: [2013-03-30 Sat 11:00]

DONE fix site tocs

CLOSED: [2013-03-30 Sat 11:11]

DONE split up docs

CLOSED: [2013-04-02 Tue 11:17]

DONE publish multiple doc versions

CLOSED: [2013-04-02 Tue 11:17]

DONE issue tracking/project hosting spring cleaning

CLOSED: [2013-04-10 Wed 15:04]

DONE clarify wish tracking

CLOSED: [2013-04-10 Wed 15:04]

DONE try trello boards

CLOSED: [2013-04-09 Tue 06:17]

DONE clarify code hosting - github ?

CLOSED: [2013-04-09 Tue 06:17]

DONE more powerful csv reading

DONE v2 rules file mockup showing new syntax

CLOSED: [2013-01-11 Fri 20:27]

DONE write down pseudo grammar

CLOSED: [2013-01-11 Fri 20:27]

DONE review status

CLOSED: [2013-01-11 Fri 20:47]

DONE parse basic directives & assignments

CLOSED: [2013-01-12 Sat 09:12]

DONE apply basic directives & assignments

CLOSED: [2013-01-13 Sun 11:01]

DONE directives

CLOSED: [2013-01-13 Sun 10:58]

DONE field assignments

CLOSED: [2013-01-13 Sun 10:58]

DONE code review

CLOSED: [2013-01-12 Sat 09:41]

DONE code cleanup

CLOSED: [2013-01-12 Sat 11:40]

DONE conform to new naming

CLOSED: [2013-01-12 Sat 11:40]

DONE clarify rules type

CLOSED: [2013-01-12 Sat 10:57]

DONE simplify

CLOSED: [2013-01-12 Sat 11:40]

DONE get it parsing test rules again

CLOSED: [2013-01-12 Sat 11:05]

DONE get it printing correct parsed value

CLOSED: [2013-01-12 Sat 11:05]

DONE cleanup

CLOSED: [2013-01-12 Sat 11:40]

DONE generate field values from templates

CLOSED: [2013-01-13 Sun 10:58]

DONE date-format & date

CLOSED: [2013-01-13 Sun 01:00]

DONE other fields

CLOSED: [2013-01-13 Sun 10:58]

DONE 1-based field numbering

CLOSED: [2013-01-13 Sun 11:01]

DONE conditional assignments

CLOSED: [2013-01-26 Sat 05:47]

DONE parsing

CLOSED: [2013-01-14 Mon 07:27]

DONE review doc

CLOSED: [2013-01-13 Sun 11:37]

DONE update grammar

CLOSED: [2013-01-13 Sun 11:39]

DONE multiple assignments

CLOSED: [2013-01-13 Sun 12:55]

DONE multiple patterns

CLOSED: [2013-01-13 Sun 13:36]

DONE clarify syntax

CLOSED: [2013-01-13 Sun 14:20]

DONE update grammar

CLOSED: [2013-01-14 Mon 07:13]

DONE clarify backwards compatibility

CLOSED: [2013-01-14 Mon 07:13]

DONE lighter conditional syntax (omit field name)

CLOSED: [2013-01-14 Mon 07:27]

DONE functionality

CLOSED: [2013-01-26 Sat 05:47]

DONE clarify spec

CLOSED: [2013-01-14 Mon 09:33]

DONE implement

CLOSED: [2013-01-14 Mon 09:33]

DONE test

CLOSED: [2013-01-26 Sat 05:47]

DONE fix speed

CLOSED: [2013-01-26 Sat 05:46]

DONE review/try regex libs

CLOSED: [2013-01-26 Sat 05:29]

DONE profile different variants

CLOSED: [2013-01-26 Sat 05:29]

DONE handle malformed regexps as well as before

CLOSED: [2013-01-26 Sat 05:46]

DONE fix warnings

CLOSED: [2013-01-26 Sat 15:03]

DONE field name list

CLOSED: [2013-01-26 Sat 15:03]

DONE implement assignment by field name list

CLOSED: [2013-01-26 Sat 14:58]

DONE add a date field assignment per its position

CLOSED: [2013-01-26 Sat 14:39]

DONE refactor

CLOSED: [2013-01-26 Sat 14:51]

DONE add multiple fields without hard coding

CLOSED: [2013-01-26 Sat 14:58]

DONE add all the right fields

CLOSED: [2013-01-26 Sat 14:58]

DONE skip

CLOSED: [2013-01-26 Sat 15:38]

DONE set currency from csv

CLOSED: [2013-03-19 Tue 06:17]

DONE set amount sign based on another field's content (type=debit|credit)

CLOSED: [2013-01-26 Sat 16:57]

DONE set amount based on two fields (amount in, amount out)

CLOSED: [2013-03-19 Tue 06:17]

DONE parse parenthesised amounts as negative

CLOSED: [2013-01-27 Sun 17:32]

DONE user friendliness

CLOSED: [2013-02-11 Mon 12:43]

DONE robust parsing

CLOSED: [2013-01-27 Sun 07:32]

DONE clear-ish errors

CLOSED: [2013-02-11 Mon 10:16]

DONE catalog errors

CLOSED: [2013-02-09 Sat 15:18]

DONE missing date/amount rule error

CLOSED: [2013-02-09 Sat 09:32]

DONE better amount parse errors

CLOSED: [2013-02-09 Sat 10:12]

DONE clean up csv parsing/validation

CLOSED: [2013-01-28 Mon 08:50]

DONE clean up rules parsing/validation

CLOSED: [2013-01-28 Mon 08:50]

DONE no amount assignment

CLOSED: [2013-01-29 Tue 06:50]

DONE amount field unparseable

CLOSED: [2013-02-09 Sat 10:12]

DONE confusing amount parse error for header line

CLOSED: [2013-02-09 Sat 11:47] hledgerdev: Error, could not parse amount "" ((line 1, column 1): unexpected end of input expecting left-symbol amount, right-symbol amount or no-symbol amount) in ["Date","Details","Debit","Credit","Balance"]

rules: #

DONE could not parse amount in (data line)

CLOSED: [2013-02-09 Sat 11:48] hledgerdev: Error, could not parse amount "" ((line 1, column 1): unexpected end of input expecting left-symbol amount, right-symbol amount or no-symbol amount) in ["07/12/2012","LODGMENT 529898","","10.0","131.21"]

rules:

DONE poor error on empty amount csv field

CLOSED: [2013-02-09 Sat 14:03]

DONE better date parse errors

CLOSED: [2013-02-09 Sat 11:26]

DONE don't silently misparse d/m/y

CLOSED: [2013-02-09 Sat 13:34]

DONE why x rule triggering ?

CLOSED: [2013-02-09 Sat 17:28]

DONE unclear error when conditional block assignment has no field name

CLOSED: [2013-02-09 Sat 17:40]

show what's happening
DONE fix parsing of %10 and fields: …amount… as %1 in .wepay-in.csv.rules

CLOSED: [2013-03-14 Thu 16:19]

DONE interpolate by name

CLOSED: [2013-03-14 Thu 17:33]

DONE skip-lines -> skip

CLOSED: [2013-03-14 Thu 17:36]

DONE amount-in/out

CLOSED: [2013-03-15 Fri 17:45]

DONE value-less skip

CLOSED: [2013-03-15 Fri 17:45]

DONE make if operator optional

CLOSED: [2013-03-19 Tue 06:47]

DONE docs

CLOSED: [2013-03-29 Fri 15:20]

DONE reorganize file format docs

CLOSED: [2013-02-11 Mon 12:45]

DONE review/record writing tips

CLOSED: [2013-03-14 Thu 16:28]

DONE csv introduction

CLOSED: [2013-03-14 Thu 17:54]

DONE convert to hakyll 4

CLOSED: [2013-03-14 Thu 17:54]

DONE serve manual from hub

CLOSED: [2013-03-13 Wed 11:03]

DONE rewrite intro

CLOSED: [2013-03-17 Sun 16:15]

DONE rewrite rules reference

CLOSED: [2013-03-19 Tue 06:15]

DONE update manual

CLOSED: [2013-03-29 Fri 14:06]

DONE update grammar

CLOSED: [2013-03-29 Fri 14:37]

DONE clarify csv / journal entry / transaction terminology

CLOSED: [2013-03-19 Tue 06:15]

DONE clarify directives / assignments / rules terminology

CLOSED: [2013-03-29 Fri 15:16]

DONE clarify account directives

CLOSED: [2013-03-29 Fri 15:16]

DONE clarify optional syntax (:, if, fields)

CLOSED: [2013-03-29 Fri 15:16]

DONE clarify upgrade process

CLOSED: [2013-03-30 Sat 10:35]

TODO update/add examples

https://gist.github.com/simonmichael/5153401 bofi example

no rules file example
rules file example
how to archive/import the journal data more permanently
DONE update default rules

CLOSED: [2013-03-29 Fri 15:19]

DONE testing

CLOSED: [2013-03-29 Fri 15:51]

DONE wells fargo checking

CLOSED: [2013-01-27 Sun 17:25]

DONE western federal

CLOSED: [2013-01-27 Sun 18:51]

DONE b of i

CLOSED: [2013-03-19 Tue 06:17]

DONE wepay

CLOSED: [2013-03-19 Tue 06:17]

DONE paypal

CLOSED: [2013-03-29 Fri 15:24]

DONE mint

CLOSED: [2013-03-29 Fri 15:39]

DONE ynab

CLOSED: [2013-03-29 Fri 15:46]

wescom
DONE commits

CLOSED: [2013-03-29 Fri 16:10]