hledger/NOTES.org

3621 lines
143 KiB
Org Mode
Raw Normal View History

hledger project notes
2007-07-02 18:36:58 +04:00
* about
2012-10-16 23:48:37 +04:00
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
2012-05-29 22:35:08 +04:00
2012-05-14 23:19:34 +04:00
* 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
2012-01-30 09:03:41 +04:00
"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
2012-01-30 09:03:41 +04:00
"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
2012-01-30 09:03:41 +04:00
ALL THAT'S NEEDED IS THE DESIRE TO BE HEARD. THE WILL TO LEARN. AND THE
ABILITY TO SEE. --Scott McCloud, Understanding Comics
2010-07-14 01:55:43 +04:00
2012-10-16 23:48:37 +04:00
"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
2012-01-30 09:03:41 +04:00
** 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
2012-01-30 09:03:41 +04:00
** other docs
*** http://en.wikibooks.org/wiki/Accounting
*** http://books.google.com/books?id=4V8pZmpwmBYC&lpg=PP1&dq=analysis%20patterns&pg=PA95#v=onepage&q&f=false
*** data representation
**** http://www.python.org/dev/peps/pep-0327/
**** http://www.n-heptane.com/nhlab/repos/Decimal/
**** http://www.n-heptane.com/nhlab/repos/Decimal/Money.hs
**** http://www2.hursley.ibm.com/decimal/
*** lwn grumpy editor articles
**** http://lwn.net/Articles/149383/
**** http://lwn.net/Articles/153043/
**** http://lwn.net/Articles/233627/
**** http://lwn.net/Articles/314577/
**** http://lwn.net/Articles/387967/ (free after 5/27)
*** hledger ghci examples
This is the main object you'll deal with as a user of the Ledger
library.
2012-01-30 09:03:41 +04:00
The most useful functions also have shorter, lower-case aliases for easier
interaction. Here's an example:
2012-02-23 19:28:36 +04:00
> > import Hledger.Data
> > j <- readJournal "sample.ledger"
> > let l = journalToLedger nullfilterspec j
> > accountnames l
> ["assets","assets:bank","assets:bank:checking","assets:bank: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:bank: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:bank: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, ...
2012-02-23 19:28:36 +04:00
*** ledger budgeting/forecasting
seanh:
2012-02-23 19:28:36 +04:00
With `--budget` you can compare your budgeted transactions to your
actual transactions and see whether you are under or over your budget.
2012-02-23 19:28:36 +04:00
The way it works is this: say you have a budget entry that moves £50
from Assets into Expenses:Cash every week:
2012-02-23 19:28:36 +04:00
~ Weekly
Expenses:Cash £50
Assets
2012-02-23 19:28:36 +04:00
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.
2011-04-22 18:05:27 +04:00
For example:
2011-04-22 18:05:27 +04:00
ledger --budget balance '^expenses'
2011-04-22 18:05:27 +04:00
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.
2011-04-22 18:05:27 +04:00
You can do the same with register and get a print out of each
transaction (budget entries and real transactions) with a running total:
2011-04-22 18:05:27 +04:00
ledger --budget register '^expenses'
2011-04-22 18:05:27 +04:00
And you can produce weekly, monthly or yearly budget reports:
2011-04-22 18:05:27 +04:00
ledger --budget --weekly register '^expenses'
ledger --budget --monthly register '^expenses'
ledger --budget --yearly register '^expenses'
2011-04-22 18:05:27 +04:00
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).
2011-04-22 18:05:27 +04:00
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.
2011-04-22 18:05:27 +04:00
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:
2011-04-22 18:05:27 +04:00
ledger --forecast 'd<[2012]' register '^assets' '^liabilities'
2011-04-22 18:05:27 +04:00
Or to see how your expenses will add up:
2011-04-22 18:05:27 +04:00
ledger --forecast 'd<[2012]' register '^expenses'
2011-04-22 18:05:27 +04:00
*** essential/getting started info
2010-12-11 01:10:05 +03:00
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
2010-12-11 01:10:05 +03:00
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)
2010-12-11 01:10:05 +03:00
so should things like income be a forever-decreasing value?
yes
2010-12-11 01:10:05 +03:00
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
2010-12-11 01:10:05 +03:00
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
2010-12-11 01:10:05 +03:00
what about loans? Those are liabilities, right?
yes
2010-12-11 01:10:05 +03:00
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)
2010-12-11 01:10:05 +03:00
2010-03-19 23:01:26 +03:00
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
2010-03-19 23:01:26 +03:00
a catalog of standard bookkeeping entries for typical real-world transactions is really helpful and worth searching for
2009-07-10 21:24:51 +04:00
*** software architecture
http://domaindrivendesign.org/resources/ddd_terms
http://stackoverflow.com/questions/6398996/good-haskell-source-to-read-and-learn-from
*** http://www.quora.com/Mint-com/best_questions
*** bitcoin
**** http://cryptome.org/0004/bitcoin-triple.htm
**** http://forum.bitcoin.org/index.php?topic=2609.0
*** selinger article on currency & capital gains accounting
http://www.mscs.dal.ca/~selinger/accounting/tutorial.html#1.2
** other software
2012-10-16 23:48:37 +04:00
*** http://easybooksapp.com/
*** http://gnucash.org
*** http://www.xtuple.com/postbooks
*** http://weberp.org
**** http://www.weberp.org/weberp/doc/Manual/ManualContents.php
*** http://www.clientsandprofits.com
2013-03-30 03:10:25 +04:00
*** http://www.youneedabudget.com/
*** https://indinero.com/
** 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.
+
2012-05-14 23:19:34 +04:00
*** 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).
2012-05-14 23:19:34 +04:00
We do not bother with any local tasks like income tax, vat or
statistical filing, invoicing and the like.
2012-05-14 23:19:34 +04:00
** 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 ?)
2012-05-14 23:19:34 +04:00
*** 2010/8
**** $ $ could handle templates as well; drop ^ ^
**** @ @ could recognise tuples automatically; drop ?
**** why !: : for conditional attributes ? How about !? ?
2012-05-14 23:19:34 +04:00
** snippets
2012-05-14 23:19:34 +04:00
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)
2012-05-14 23:19:34 +04:00
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)
2012-05-14 23:19:34 +04:00
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)
2012-05-14 23:19:34 +04:00
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;} */
2009-01-20 11:21:50 +03:00
/* 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} */
2009-01-20 11:21:50 +03:00
/* 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:car: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\"?>");
2009-01-20 11:21:50 +03:00
-- $link = mysql_pconnect($mysql_host, $mysql_user, $mysql_pasw);
-- $db = mysql_select_db ($mysql_db);
-- if (!isset($_GET["pos"])) $_GET["pos"]=0;
2009-01-20 11:21:50 +03:00
-- //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);
2009-01-20 11:21:50 +03:00
2009-01-20 11:21:50 +03:00
-- //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>");
-- }
-- ?>
2009-01-20 11:21:50 +03:00
** 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'
2011-08-20 20:10:38 +04:00
**** 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\SIMON\LOCALS~1\Temp\haskeline-0.6.3.24132\haskeline-0.6.3.2\Setup.hs, C:\DOCUME~1\SIMON\LOCALS~1\Temp\haskeline-0.6.3.24132\haskeline-0.6.3.2\dist\setup\Main.o )
Linking C:\DOCUME~1\SIMON\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\System\Console\Haskeline\Backend\Win32_hsc_make.o:Win32_hsc_make.c:(.text+0x47): undefined reference to `_impure_ptr'
dist\build\System\Console\Haskeline\Backend\Win32_hsc_make.o:Win32_hsc_make.c:(.text+0x7b): undefined reference to `_impure_ptr'
dist\build\System\Console\Haskeline\Backend\Win32_hsc_make.o:Win32_hsc_make.c:(.text+0x93): undefined reference to `_impure_ptr'
dist\build\System\Console\Haskeline\Backend\Win32_hsc_make.o:Win32_hsc_make.c:(.text+0xc7): undefined reference to `_impure_ptr'
dist\build\System\Console\Haskeline\Backend\Win32_hsc_make.o:Win32_hsc_make.c:(.text+0xf3): undefined reference to `_impure_ptr'
dist\build\System\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\System\Console\Haskeline\Backend\Win32_hsc_make.o failed
command was: C:\HP\mingw\bin\gcc.exe -LC:\cygwin\lib -LC:\Documents and Settings\Simon\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\integer-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\System\Console\Haskeline\Backend\Win32_hsc_make.o -o dist\build\System\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
2011-08-20 20:10:38 +04:00
**** gtk2hs
Eduard_Munteanu> sm: gtk2hs-0.10.1 (binary), HP 2009.2.0.1 (binary too) -- if you ever need it.
2011-08-20 20:10:38 +04:00
*** wine on osx 10.6
**** enumerator
Z:\Users\simon\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 )
2011-08-20 20:10:38 +04:00
(dialog: The program touchy.exe has encountered a serious problem and needs to close...)
2011-08-20 20:10:38 +04:00
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\touchy.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)
2011-08-20 20:10:38 +04:00
2013-03-30 03:10:25 +04:00
** performance tuning
http://stackoverflow.com/questions/3276240/tools-for-analyzing-performance-of-a-haskell-program/3276557#3276557
** good list of cost of ownership questions
http://felixge.de/2013/03/07/open-source-and-responsibility.html
** 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
http://felixge.de/2013/03/07/open-source-and-responsibility.html
* 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
2011-08-20 20:10:38 +04:00
*** 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
2011-08-20 20:10:38 +04:00
Reads your ~/.journal file, or another specified by $LEDGER or -f, and
runs the specified command (may be abbreviated):
2011-08-20 20:10:38 +04:00
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
2011-08-20 20:10:38 +04:00
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
2011-08-20 20:10:38 +04:00
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.
2011-08-20 20:10:38 +04:00
**** attempts
***** original getopts
progname_cli = "hledger"
2011-08-20 20:10:38 +04:00
-- | 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"
2011-08-20 20:10:38 +04:00
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"
2011-08-20 20:10:38 +04:00
usage_options_cli = usageInfo "hledger options:" options_cli
2011-08-20 20:10:38 +04:00
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"
2011-08-20 20:10:38 +04:00
usage_cli = concat [
usage_preamble_cli
,usage_options_cli
,usage_postscript_cli
]
2011-08-20 20:10:38 +04:00
-- | 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"
]
2011-08-20 20:10:38 +04:00
-- | 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)
2011-08-20 20:10:38 +04:00
-- these make me nervous
optsWithConstructor f opts = concatMap get opts
where get o = [o | f v == o] where v = value o
2011-08-20 20:10:38 +04:00
optsWithConstructors fs opts = concatMap get opts
where get o = [o | any (== o) fs]
2011-08-20 20:10:38 +04:00
optValuesForConstructor f opts = concatMap get opts
where get o = [v | f v == o] where v = value o
2011-08-20 20:10:38 +04:00
optValuesForConstructors fs opts = concatMap get opts
where get o = [v | any (\f -> f v == o) fs] where v = value o
2011-08-20 20:10:38 +04:00
-- | 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
2011-08-20 20:10:38 +04:00
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 ([],[])
2011-08-20 20:10:38 +04:00
argsError :: String -> IO ()
argsError = ioError . userError' . (++ " Run with --help to see usage.")
2011-08-20 20:10:38 +04:00
-- | 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
2011-08-20 20:10:38 +04:00
-- | 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
2011-08-20 20:10:38 +04:00
-- | 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
2011-08-20 20:10:38 +04:00
rulesFileFromOpts :: [Opt] -> Maybe FilePath
rulesFileFromOpts opts = listtomaybe $ optValuesForConstructor RulesFile opts
where
listtomaybe [] = Nothing
listtomaybe vs = Just $ head vs
2011-08-20 20:10:38 +04:00
-- | 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
]
2011-08-20 20:10:38 +04:00
-- | 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
2011-08-20 20:10:38 +04:00
-- | 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
2011-08-20 20:10:38 +04:00
-- | 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
2012-04-12 20:10:11 +04:00
-- | 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
2012-04-12 20:10:11 +04:00
-- | 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
2012-04-12 20:10:11 +04:00
-- | 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
2012-04-12 20:10:11 +04:00
-- | 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
2012-04-12 20:10:11 +04:00
-- | 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
2012-04-12 20:10:11 +04:00
-- | Detect which date we will report on, based on --effective.
whichDateFromOpts :: [Opt] -> WhichDate
whichDateFromOpts opts = if Effective `elem` opts then EffectiveDate else ActualDate
2012-04-12 20:10:11 +04:00
-- | Were we invoked as \"hours\" ?
usingTimeProgramName :: IO Bool
usingTimeProgramName = do
progname <- getProgName
return $ map toLower progname == progname_cli_time
2012-04-12 20:10:11 +04:00
-- | 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
2012-04-12 20:10:11 +04:00
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
2012-04-12 20:10:11 +04:00
-- | 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
2012-04-12 20:10:11 +04:00
-- | 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
2012-04-12 20:10:11 +04:00
-- currentLocalTimeFromOpts opts = listtomaybe $ optValuesForConstructor CurrentLocalTime opts
-- where
-- listtomaybe [] = Nothing
-- listtomaybe vs = Just $ last vs
2012-04-12 20:10:11 +04:00
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)"
2012-04-12 20:10:11 +04:00
,"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
2012-04-12 20:10:11 +04:00
]
2012-04-12 20:10:11 +04:00
***** cmdargs implicit ADT with ifdefs
progname = "hledger"
progversion = progversionstr progname
2012-04-12 20:10:11 +04:00
progname_cli = progname
2012-04-12 20:10:11 +04:00
-- | 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"
2012-04-12 20:10:11 +04:00
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"
2012-04-12 20:10:11 +04:00
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."
]
2012-04-12 20:10:11 +04:00
-- | 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
2012-04-12 20:10:11 +04:00
-- :: -- 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]
2012-04-12 20:10:11 +04:00
} deriving (Show, Data, Typeable)
2012-04-12 20:10:11 +04:00
-- deriving instance Default Day
-- instance Default DateSpan where def = nulldatespan
-- instance Default Interval where def = NoInterval
2012-04-12 20:10:11 +04:00
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
2012-04-12 20:10:11 +04:00
-- 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
2012-04-12 20:10:11 +04:00
processOpts :: Opts -> IO Opts
processOpts opts = do
let opts' = decodeOpts opts
fixMostOptDates opts'
2012-04-12 20:10:11 +04:00
-- | 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_
}
2012-04-12 20:10:11 +04:00
-- | 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
}
2012-04-12 20:10:11 +04:00
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
2012-04-12 20:10:11 +04:00
optsError :: String -> IO ()
-- optsError s = putStrLn s >> exitWith (ExitFailure 1)
optsError = ioError . userError' . (++ " Run with --help to see usage.")
2012-04-12 20:10:11 +04:00
-- | 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)
2012-04-12 20:10:11 +04:00
-- | 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
2012-04-12 20:10:11 +04:00
-- | 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
2012-04-12 20:10:11 +04:00
-- | 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
]
2012-04-12 20:10:11 +04:00
-- | Get the value of the baseurl option, if any.
baseUrlFromOpts :: Opts -> Maybe String
baseUrlFromOpts = const Nothing -- base_url
2012-04-12 20:10:11 +04:00
-- | Get the value of the (last) port option, if any.
portFromOpts :: Opts -> Maybe Int
portFromOpts = const Nothing -- port
2011-08-20 20:10:38 +04:00
-- | 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
2011-08-20 20:10:38 +04:00
-- | Detect which date we will report on, based on --effective.
whichDateFromOpts :: Opts -> WhichDate
whichDateFromOpts opts = if effective opts then EffectiveDate else ActualDate
2011-08-20 20:10:38 +04:00
-- | 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
2011-08-20 20:10:38 +04:00
command :: Opts -> String
command = headDef "" . args_
2011-08-20 20:10:38 +04:00
patterns :: Opts -> [String]
patterns = tailDef [] . args_
2011-08-20 20:10:38 +04:00
-- | 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
2011-08-20 20:10:38 +04:00
-- | 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
2011-08-20 20:10:38 +04:00
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)"
2011-08-20 20:10:38 +04:00
,"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
2011-08-20 20:10:38 +04:00
]
***** 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
2011-08-20 20:10:38 +04:00
-- :: -- 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
2011-08-20 20:10:38 +04:00
,args_ :: [String]
2011-08-20 20:10:38 +04:00
} deriving (Show, Data, Typeable)
2011-08-20 20:10:38 +04:00
***** cmdargs explicit string map
progname = Hledger.Cli.progname ++ "-vty"
progversion = progversionstr progname
2011-08-20 20:10:38 +04:00
-- 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"
2011-08-20 20:10:38 +04:00
type Opts = Map String String
2011-08-20 20:10:38 +04:00
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
]
2011-08-20 20:10:38 +04:00
-- defopts = Hledger.Cli.defopts {
-- debug_vty = def &= help "run with no terminal output, showing console"
-- ,args_ = def &= args &= typ "PATTERNS"
-- }
-- &= program progname
-- &= summary progversion
2011-08-20 20:10:38 +04:00
getHledgerVtyOpts :: IO Opts
getHledgerVtyOpts = processArgs vtymode -- >>= processOpts >>= checkOpts
2011-08-20 20:10:38 +04:00
-- processOpts :: Opts -> IO Opts
-- processOpts = Hledger.Cli.processOpts
2011-08-20 20:10:38 +04:00
-- checkOpts :: Opts -> IO Opts
-- checkOpts = Hledger.Cli.checkOpts
2011-08-20 20:10:38 +04:00
main :: IO ()
main = do
opts <- getHledgerVtyOpts
when ("debug" `M.member` opts) $ printf "%s\n" progversion >> printf "opts: %s\n" (show opts)
return () -- runWith opts
2011-08-20 20:10:38 +04:00
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
2011-08-20 20:10:38 +04:00
in_ = M.member
2011-08-20 20:10:38 +04:00
type Opts = Map String String
2011-08-20 20:10:38 +04:00
vtyargs = flagArg (\v opts -> Right $ M.insert "PATTERNS" v opts) "query patterns"
2011-08-20 20:10:38 +04:00
vtyflags = [
flagNone ["debug-vty"] (\opts -> M.insert "debug-vty" "" opts) "run with no terminal output, showing console"
]
2011-08-20 20:10:38 +04:00
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" "")
]
2011-08-20 20:10:38 +04:00
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)
]
}
}
2011-08-20 20:10:38 +04:00
main = do
opts <- processArgs vtymode
print opts
when ("help" `in_` opts) $ putStr $ showText defaultWrap $ helpText HelpFormatDefault vtymode
2011-08-20 20:10:38 +04:00
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
2011-08-20 20:10:38 +04:00
clearedValueFromOpts opts | "cleared" `in_` opts = Just True
| "uncleared" `in_` opts = Just False
| otherwise = Nothing
2011-08-20 20:10:38 +04:00
-- 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)
2011-08-20 20:10:38 +04:00
***** 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.
2011-08-20 20:10:38 +04:00
-- 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)
2011-08-20 20:10:38 +04:00
defreportopts = ReportOpts
def
def
def
def
def
def
def
def
def
def
def
def
def
def
def
def
def
def
def
def
def
2011-08-20 20:10:38 +04:00
instance Default ReportOpts where def = defreportopts
2011-08-20 20:10:38 +04:00
-- 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
2011-08-20 20:10:38 +04:00
,reportopts_ :: ReportOpts
} deriving (Show) --, Data, Typeable)
2011-08-20 20:10:38 +04:00
defcliopts = CliOpts
def
def
def
def
def
def
def
def
def
2011-08-20 20:10:38 +04:00
instance Default CliOpts where def = defcliopts
2011-08-20 20:10:38 +04:00
-- 2. reusable/extensible command-line flags, help and initial parsing for the above.
2011-08-20 20:10:38 +04:00
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"
]
2011-08-20 20:10:38 +04:00
addflags = [
flagNone ["no-new-accounts"] (\opts -> M.insert "no-new-accounts" "" opts) ""
]
2011-08-20 20:10:38 +04:00
convertflags = [
flagReq ["rules-file"] (\v opts -> Right $ M.insert "rules-file" v opts) "FILE" ""
]
2011-08-20 20:10:38 +04:00
cliargs = flagArg (\v opts -> Right $ M.insert "args" v opts) "COMMAND [PATTERNS]"
2011-08-20 20:10:38 +04:00
type RawOpts = Map String String
2011-08-20 20:10:38 +04:00
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)
]
}
}
2012-05-29 22:35:08 +04:00
-- 3. post-processing, additional checking and conversion of raw options
-- data Opts = Opts { cliopts :: CliOpts, reportopts :: ReportOpts } deriving (Show)
-- defopts = Opts defcliopts defreportopts
2012-05-29 22:35:08 +04:00
processOpts :: RawOpts -> CliOpts
processOpts rawopts = defcliopts {
file_ = stringopt "file" rawopts
,debug_ = boolopt "debug" rawopts
,reportopts_ = defreportopts {
begin_ = maybedateopt "begin" rawopts
,end_ = maybedateopt "end" rawopts
2012-02-23 19:28:36 +04:00
}
}
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
2011-08-20 20:10:38 +04:00
testmain = do
rawopts <- processArgs progmode
print rawopts
print $ processOpts rawopts
when ("help" `in_` rawopts) $ putStr $ showText defaultWrap $ helpText HelpFormatDefault progmode
2011-08-20 20:10:38 +04:00
***** 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
2011-08-20 20:10:38 +04:00
** 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
2011-08-20 20:10:38 +04:00
2012-11-12 02:10:52 +04:00
* in progress
2012-11-20 04:28:05 +04:00
2012-11-12 02:10:52 +04:00
* next up
2012-11-20 04:28:05 +04:00
** web: code review/refactor
** web: use bootstrap
** more powerful csv conversion
** formats: ofx reader
*** clint's code
Date: Sun, 18 Sep 2011 12:26:16 -0400
From: Clint Adams <clint@softwarefreedom.org>
To: hledger@googlegroups.com
Subject: OFX conversion
Message-ID: <20110918162616.GA18874@softwarefreedom.org>
MIME-Version: 1.0
User-Agent: Mutt/1.5.20 (2009-06-14)
X-Original-Sender: clint@softwarefreedom.org
X-Original-Authentication-Results: gmr-mx.google.com; spf=pass (google.com:
domain of clint@softwarefreedom.org designates 216.27.154.199 as permitted
sender) smtp.mail=clint@softwarefreedom.org
Reply-To: hledger@googlegroups.com
Precedence: list
Mailing-list: list hledger@googlegroups.com; contact hledger+owners@googlegroups.com
List-ID: <hledger.googlegroups.com>
X-Google-Group-Id: 895107692464
List-Post: <http://groups.google.com/group/hledger/post?hl=en_US>, <mailto:hledger@googlegroups.com>
List-Help: <http://groups.google.com/support/?hl=en_US>, <mailto:hledger+help@googlegroups.com>
List-Archive: <http://groups.google.com/group/hledger?hl=en_US>
Sender: hledger@googlegroups.com
List-Subscribe: <http://groups.google.com/group/hledger/subscribe?hl=en_US>, <mailto:hledger+subscribe@googlegroups.com>
List-Unsubscribe: <http://groups.google.com/group/hledger/subscribe?hl=en_US>, <mailto:hledger+unsubscribe@googlegroups.com>
Content-Type: text/plain; charset=iso-8859-1
Content-Disposition: inline
Content-Transfer-Encoding: 8bit
This is definitely suboptimal but it seems to work on
the OFX 1.0.2 output from AmEx.
{-# LANGUAGE Arrows, NoMonomorphismRestriction #-}
import Text.XML.HXT.Core
import Text.Printf (printf)
import Data.List (groupBy)
import Data.List.Split (splitOn)
import Data.Maybe (fromMaybe)
import Data.Time.Calendar (Day (ModifiedJulianDay))
import Data.Time.Format (formatTime)
import Data.Time.LocalTime (LocalTime (LocalTime), TimeOfDay (TimeOfDay))
import Data.Time.Parse (strptime)
import System.Locale (defaultTimeLocale)
import System.Process (readProcessWithExitCode)
import Hledger.Cli.Format (FormatString (FormatField), Field (FieldNo))
import Hledger.Cli.Convert
normAmount :: String -> String
normAmount amt | amt == "" = ""
| otherwise = printf "%.2f" (read amt :: Double)
compressWhitespace :: String -> String
compressWhitespace x = map head $ groupSpaces x
where groupSpaces "" = [""]
groupSpaces x = groupBy (\x y -> x==' ' && y==' ') x
data Transaction = Transaction
{ trnType, dtUser, dtPosted, trnAmt, fitId, refNum, name, memo :: String }
deriving (Show, Eq)
-- this doesn't get the timezone right
ofxDateParse :: String -> String
ofxDateParse x = formatTime defaultTimeLocale "%Y-%m-%d" (fst (fromMaybe (LocalTime (ModifiedJulianDay 100) (TimeOfDay 0 0 0), "") (strptime "%Y%m%d%H%M%S.%OS" x)))
parseFakeXML string = readString [ withValidate no
, withRemoveWS yes
] string
atTag tag = deep (isElem >>> hasName tag)
text = getChildren >>> getText
textAtTag tag = atTag tag >>> text
getTransactions = atTag "STMTTRN" >>>
proc l -> do
trnType <- textAtTag "TRNTYPE" -< l
dtUser <- textAtTag "DTUSER" -< l
dtPosted <- textAtTag "DTPOSTED" -< l
trnAmt <- textAtTag "TRNAMT" -< l
fitId <- textAtTag "FITID" -< l
refNum <- textAtTag "REFNUM" -< l
name <- textAtTag "NAME" -< l
memo <- textAtTag "MEMO" -< l
returnA -< Transaction
{ trnType = trnType,
dtUser = ofxDateParse dtUser,
dtPosted = ofxDateParse dtPosted,
trnAmt = trnAmt,
fitId = fitId,
refNum = refNum,
name = name,
memo = memo }
ofxrules = CsvRules {
dateField=Just 0,
dateFormat=Nothing,
statusField=Nothing,
codeField=Nothing,
descriptionField=[FormatField False Nothing Nothing (FieldNo 2)],
amountField=Just 1,
inField=Nothing,
outField=Nothing,
currencyField=Nothing,
baseCurrency=Nothing,
accountField=Nothing,
account2Field=Nothing,
effectiveDateField=Nothing,
baseAccount="Liabilities:American Express",
accountRules=[]
}
txnToCsvRecord :: Transaction -> CsvRecord
txnToCsvRecord x = [dtUser x, normAmount (trnAmt x), compressWhitespace (name x) ++ "(" ++ refNum x ++ ")", fitId x, memo x]
printTxnWithComment :: CsvRecord -> IO ()
printTxnWithComment x = putStrLn ("; " ++ x !! 3 ++ " - " ++ x !! 4) >> printTxn False ofxrules x
main = do
filecontents <- readFile "/tmp/ofx.ofx"
let splitfilecontents = splitOn "\n\n" filecontents
let ofxheader = head splitfilecontents
let ofxsgml = splitfilecontents !! 1
(_, fakexml, _) <- readProcessWithExitCode "/usr/bin/sgml2xml" [] ofxsgml
transes <- runX (parseFakeXML fakexml >>> getTransactions)
let records = map txnToCsvRecord transes
mapM_ (printTxnWithComment) records
2012-11-12 02:10:52 +04:00
* backlog
** errors
*** 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: decimal point/thousands separator confusion ? 1d
<<<
2011/09/30
a $1,000,000.00
b
2011-08-20 20:10:38 +04:00
2011/09/30 x
a $1,2
b
2012-02-23 19:28:36 +04:00
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-08-20 20:10:38 +04:00
2011/09/30 x
a $1.20
b $-1.20
2011-08-20 20:10:38 +04:00
2011/09/30 y
a $1.20
b $-1.20
2011-08-20 20:10:38 +04:00
*** 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
2011-08-20 20:10:38 +04:00
*** 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
2011-08-20 20:10:38 +04:00
*** 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)
2011-08-20 20:10:38 +04:00
$ 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-08-20 20:10:38 +04:00
2011/09/30
a $1,000,000.00
b
2011-08-20 20:10:38 +04:00
2011/09/30 x
a $1,2
b
2011-08-20 20:10:38 +04:00
2011/09/30 y
a $1.2
b
2011-08-20 20:10:38 +04:00
2011/09/30 z
a 1,000
b
2011-08-20 20:10:38 +04:00
*** convert: 49 convert should report rules file parse errors better 1d
*** 25 hledger in windows console does not print non-ascii characters 3d
http://stackoverflow.com/questions/10779149/unicode-console-i-o-in-haskell-on-windows
http://hackage.haskell.org/trac/ghc/ticket/4471
*** journalAddFile is called in reverse order of includes 2
** documentation, marketing
*** 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.
2011-08-20 20:10:38 +04:00
hledger is a reporting tool for accounting transactions stored in a simple human-editable text format.
2011-08-20 20:10:38 +04:00
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.
2011-08-20 20:10:38 +04:00
hledger is primarily a reporting tool, but it can also help you add transactions to the journal, or convert from other data formats.
2011-08-20 20:10:38 +04:00
hledger is a haskell port and friendly fork of John Wiegley's c++ ledger tool.
2011-08-20 20:10:38 +04:00
hledger aims to be a reliable, practical, useful tool for (slightly geeky) users and a reusable library for haskell programmers interested in finance.
2011-08-20 20:10:38 +04:00
hledger is quite simple in essence, aiming to be a reliable low-level parsing-and-reporting tool that doesn't get in your way.
2011-08-20 20:10:38 +04:00
For some, it is a less complex, less expensive, more efficient alternative to Quicken or Quickbooks.
2011-08-20 20:10:38 +04:00
hledger is available for free under the GNU General Public License.
2011-08-20 20:10:38 +04:00
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.
2011-08-20 20:10:38 +04:00
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.
2011-08-20 20:10:38 +04:00
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.
2011-08-20 20:10:38 +04:00
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.
2011-08-20 20:10:38 +04:00
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.
2011-08-20 20:10:38 +04:00
hledger is written in the Haskell programming language;
it demonstrates a pure functional implementation of ledger.
2011-08-20 20:10:38 +04:00
*** 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
2011-08-20 20:10:38 +04:00
**** 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:
2011-08-20 20:10:38 +04:00
1. Equity accounts accommodate your previous years of not maintaining accounts (fixed, probably negative)
2011-08-20 20:10:38 +04:00
2. Expense accounts become more and more positive (unavoidably)
2011-08-20 20:10:38 +04:00
3. Income accounts become more and more negative (on payday)
2011-08-20 20:10:38 +04:00
4. Assets Accounts become more and more positive (in good times)
2011-08-20 20:10:38 +04:00
5. Liability account become more positive (in good times, when you pay them off) and more negative (when you use them to buy things).
2011-08-20 20:10:38 +04:00
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
**** license change ?
**** 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.
2011-09-02 05:01:16 +04:00
* grants
2011-08-20 20:10:38 +04:00
How to find possible grant sources ?
2011-08-20 20:10:38 +04:00
* 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
2012-02-23 19:28:36 +04:00
* flexible, low administration, encourages trust
2011-08-20 20:08:52 +04:00
* 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).
2011-08-20 20:08:52 +04:00
* pro
2011-08-20 20:08:52 +04:00
* funders receive direct benefit
* bounties using fundable.org (eg)
2011-08-23 17:22:57 +04:00
A more organised form of the above, perhaps facilitating trust,
co-funding and larger bounties.
2011-08-23 17:22:57 +04:00
* pro
2011-08-23 17:22:57 +04:00
* proven process developed by others
2011-08-23 17:22:57 +04:00
* con
2011-04-18 01:58:00 +04:00
* fundable takes a cut
2011-04-18 01:58:00 +04:00
* hosted service
2010-11-15 17:54:07 +03:00
Offer hosted and managed ledgers, perhaps with premium features, for
a monthly fee
2010-03-19 23:01:26 +03:00
* pro
2009-07-10 21:24:51 +04:00
* proven model
* clear benefit to customers, especially non-technies
2012-01-30 09:03:41 +04:00
* con
* success of free/self-installed version competes with hosting service
* some will avoid web-hosting their financial data
2012-01-30 09:03:41 +04:00
* customisation
2012-01-30 09:03:41 +04:00
Offer per-user customisations, possibly to be merged in the trunk,
for a fee
2012-01-30 09:03:41 +04:00
* support
2012-01-30 09:03:41 +04:00
Offer user/developer support for a fee
2012-01-30 09:03:41 +04:00
* training
2008-05-27 01:13:54 +04:00
Offer application and/or financial training for a fee
2010-07-09 06:07:42 +04:00
* profit sharing/tithing
2010-07-09 06:07:42 +04:00
Each period (quarter, half-year, year), donate 10% (eg) to project
contributors and/or supporting projects
2010-07-09 06:07:42 +04:00
* transparent funding
2010-07-09 06:07:42 +04:00
Funding and usage of funds is published on the web as a ledger
2010-07-09 06:07:42 +04:00
* opaque funding
2010-07-09 06:07:42 +04:00
All funding and spending need not be made public
2012-02-23 19:28:36 +04:00
strengths
=========
hledger has some aptitudes in this area:
2010-07-09 06:07:42 +04:00
* 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
2010-07-09 06:07:42 +04:00
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)
2010-07-09 06:07:42 +04:00
competitors/fellow niche inhabitants
====================================
* web apps
* netsuite
* sql-ledger, ledgersmb
* wesabe
* ...
* desktop apps
* quickbooks
* quicken
* ms money
* grisbi
* gnucash
* excel
* ledger!
* ...
2010-07-09 06:07:42 +04:00
***** 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
2010-07-09 06:07:42 +04:00
**** home edition
**** real-time project ledger
**** in-place transaction editing fund drive
2010-07-09 06:07:42 +04:00
Fund drive: hledger-web in-place transaction editing
2010-07-09 06:07:42 +04:00
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.
2010-07-09 06:07:42 +04:00
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.
2010-07-09 06:07:42 +04:00
Plan: do the front-end javascript and backend haskell work required to
support:
2010-07-09 06:07:42 +04:00
- 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
2010-07-09 06:07:42 +04:00
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:
2010-07-09 06:07:42 +04:00
- 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
2010-07-09 06:07:42 +04:00
Also, 10% of the amount raised will be tithed to three contributing
projects or developers (ledger and two others of my choice.)
2010-07-09 06:07:42 +04:00
This project will go forward if
2010-07-09 06:07:42 +04:00
[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
2010-11-15 17:54:07 +03:00
*** 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:
2010-11-15 17:54:07 +03:00
$ 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
2010-11-15 17:54:07 +03:00
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
2010-11-15 17:54:07 +03:00
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:
2010-11-15 17:54:07 +03:00
$ 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
2010-11-15 17:54:07 +03:00
Even if --no-rounding is passed in:
2010-11-15 17:54:07 +03:00
$ 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
2010-11-15 17:54:07 +03:00
Is there something off with how the data aboce is set up? Should I be
using be more place holders?
*** performance
**** speed, benchmark tests
**** memory usage
*** 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
*** 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
http://ajaxcssblog.com/jquery/url-read-request-variables/
*** 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
*** inspiration
http://community.haskell.org/~ndm/downloads/paper-hoogle_overview-19_nov_2008.pdf -> Design Guidelines
2012-11-20 04:28:05 +04:00
** features
*** add: don't offer record txn option in account N prompt if it's not balanced yet
*** add: rewrite a short description (trader) to the full description from the matched txn (trader joe's) ?
*** add: try wizards, vty-ui libs ?
*** add: would be nice to create the journal file only if a txn is actually recorded
*** balance: --depth with --flat should show aggregate balances including the non-displayed deeper accounts
*** balance: try indenting amounts
$260.00 expenses
$260.00 rent
*** balancesheet, incomestatement, cashflow: real-world testing
*** cli, web: consistent query language with grouping and nesting
ledger's query syntax: http://ledger-cli.org/3.0/doc/ledger.1.html
**** draft 1:
Filter patterns restrict the postings/transactions that are displayed.
(They often reduce the amount of processing work hledger has to do, as well.)
A pattern is a string or regular expression, usually with a prefix specifying the type of match to do.
The supported prefixes are:
acct:PAT match postings affecting accounts whose name matches PAT
otheracct:PAT match the other postings in transactions with an acct match (like ledger's --related)
anyacct:PAT match all postings in transactions with an acct match (union of acct and otheracct)
desc:PAT match postings whose description matches PAT
status:PAT match postings whose cleared status matches PAT
code:PAT match postings whose transaction code matches PAT
tag:PAT match postings with a metadata tag whose name matches PAT
tag:TAG=PAT match postings with a metadata tag named TAG whose value matches PAT
from:DATE match postings on or after DATE (like --begin)
to:DATE match postings before DATE (like --end)
in:PERIOD match postings during PERIOD (like --period)
(or ?
begin:DATE match postings on or after DATE (like --begin)
end:DATE match postings before DATE (like --end)
period:PERIOD match postings during PERIOD (like --period)
)
Prefixes have a short form which is their first letter, except for tag and anyacct.
Prefix-less patterns are treated like acct: patterns, except by the
register command which treats them as otheracct: .
Patterns containing whitespace must be enclosed in quotes.
Matches are always case-insensitive.
Matches are always substring matches (except for TAG); to match exactly,
wrap the pattern in ^ and $.
A posting's date, status, code, etc. is usually (but not always) that of
its containing transaction.
Any of these may be further prefixed with not: for an inverse match.
Filter patterns may be combined with AND, OR, and parentheses. OR is
assumed by default.
(previously:
When you specify multiple filter patterns, hledger generally selects the
items which match:
any of the account patterns AND any of the description patterns
The print command selects transactions which
match any of the description patterns AND have any postings matching any
of the positive account patterns AND have no postings matching any of
the negative account patterns
)
*** cli: --empty -> --show-zero-accounts, --show-empty-parents ? Make it default ?
*** cli: --no-elide -> --empty-parents ? Make it the default ?
$260.00 expenses
$260.00 rent
*** cli: --related
*** cli: -X/--show-in-commodity
cf http://bugs.ledger-cli.org/show_bug.cgi?id=538
*** cli: accept multiple journal file options
*** cli: better control of output format/layout
**** register --format, generalise --format
**** --wide ? window width sensitive ?
**** more tidy/consistent layout from print
**** --output-layout=ledger|traditional
**** --output-format=text|html|pdf
*** cli: inacct: on command line
hledger -f demo.journal reg inacct:expenses:food:pets date:2010/8/25
2010/08/25 catfood expenses:food:pets $10.00 $10.00
assets:cash $-10.00 0
2012-11-20 04:28:05 +04:00
*** docs: --help-web and web ui help links that go to online help, with paragraph comments & chat
cf clients & profits interactive user guide, php.net, realworldhaskell etc.
*** docs: better intro, less wall-of-text, separate tutorial & reference sections
*** docs: easy hledger.org/topic help urls, like php.net
*** formats: easier timelog formats
*** formats: better csv conversion
**** better default rules file
**** consistent multi-field formats allowed for any field
**** optionally generate single-entry txns
**** optionally set final amount blank
**** parse amounts like ($nnn.nn)
**** parse HH:MM[:SS] as an amount, converting to decimal hours
**** reuse add's history awareness
**** select one or both accounts based on csv fields
**** use default rules when converting stdin with no --rules
**** warn and ignore unparsed rows ?
*** formats: ofx protocol downloader
*** formats: IIF/quickbooks writer
*** formats: gnucash reader
*** formats: qif reader
**** clint's code
Date: Tue, 25 Oct 2011 11:46:24 -0400
From: Clint Adams <clint@softwarefreedom.org>
To: hledger@googlegroups.com
Cc: thomas@marketpsychdata.com, jjenning@fastmail.fm
Subject: Re: QIF parsing
Message-ID: <20111025154624.GA3097@softwarefreedom.org>
References: <20111006164952.GA734@softwarefreedom.org>
MIME-Version: 1.0
In-Reply-To: <20111006164952.GA734@softwarefreedom.org>
User-Agent: Mutt/1.5.21 (2010-09-15)
X-Original-Sender: clint@softwarefreedom.org
X-Original-Authentication-Results: gmr-mx.google.com; spf=pass (google.com:
domain of clint@softwarefreedom.org designates 207.86.247.70 as permitted
sender) smtp.mail=clint@softwarefreedom.org
Reply-To: hledger@googlegroups.com
Precedence: list
Mailing-list: list hledger@googlegroups.com; contact hledger+owners@googlegroups.com
List-ID: <hledger.googlegroups.com>
X-Google-Group-Id: 895107692464
List-Post: <http://groups.google.com/group/hledger/post?hl=en_US>, <mailto:hledger@googlegroups.com>
List-Help: <http://groups.google.com/support/?hl=en_US>, <mailto:hledger+help@googlegroups.com>
List-Archive: <http://groups.google.com/group/hledger?hl=en_US>
Sender: hledger@googlegroups.com
List-Subscribe: <http://groups.google.com/group/hledger/subscribe?hl=en_US>, <mailto:hledger+subscribe@googlegroups.com>
List-Unsubscribe: <http://groups.google.com/group/hledger/subscribe?hl=en_US>, <mailto:hledger+unsubscribe@googlegroups.com>
Content-Type: text/plain; charset=iso-8859-1
Content-Disposition: inline
Content-Transfer-Encoding: 8bit
X-Truedomain-Domain: googlegroups.com
X-Truedomain-SPF: Neutral (mx4: 173.255.219.222 is neither permitted nor denied by domain of googlegroups.com)
X-Truedomain-DKIM: Pass
X-Truedomain-ID: 16FADD416626EE6BDC6CCBB61A94EA31
X-Truedomain: Neutral
2010-12-11 01:10:05 +03:00
I had to update my QIF converter for modern hledger; included below.
2010-05-20 23:14:44 +04:00
Thomas, I didn't see your reply because I'm not subscribed to
this Google Group. I believe that QuickBooks uses OFX, not QIF,
so you'd be more interested in
2011-09-28 04:32:56 +04:00
http://groups.google.com/group/hledger/browse_thread/thread/e03ccc655347ba72
2011-09-28 04:32:56 +04:00
or
2011-09-28 04:32:56 +04:00
http://www.dingoskidneys.com/~jaredj/
2011-09-28 04:32:56 +04:00
2012-11-20 04:28:05 +04:00
------8<-------
import Text.Parsec
import Text.Parsec.String
import Control.Monad.State as State
import System (getArgs)
import Data.List (groupBy)
import Data.Maybe (fromMaybe)
import qualified Data.Map as Map
import Text.Printf (printf)
import Hledger.Cli.Format (FormatString (FormatField), Field (FieldNo))
import Hledger.Cli.Convert
qifFile :: GenParser Char st (String,[[TransactionDetail]])
qifFile = do
skipMany newline
dtype <- typeHeader
newline
trans <- endBy1 transaction recordSep
return $ (dtype,trans)
typeHeader :: GenParser Char st String
typeHeader = do
string "!Type:"
dataType
dataType :: GenParser Char st String
dataType = do string "Cash"
<|> string "Bank"
<|> string "CCard"
<|> string "Invst"
<|> string "Oth A"
<|> string "Oth L"
<|> string "Invoice"
transaction :: GenParser Char st [TransactionDetail]
*** parsing: accept all real-world ledger files
As far as I know it currently accepts all ledger 2.6-era files.
Add support for ledger 3 file format as/when that stabilises.
It would be nice to optionally semi/automatically submit parse error reports when they happen
*** parsing: alias directives should be modified by account directives
*** parsing: allow price record for null commodity, eg with quotes
P 2009/1/1 "" 0.5h
and why doesn't this work ? time.journal:
P 2010/9/27 h 1
$ hledger -f time.journal bal -p aug -B
1
17.75h work:jobs
1
17.50h clearview
1 60 clear glass thermal data
0.25h admin:cheque issue
12.75h backups/hosting
2.00h cleanup
1.00h move plan
2.25h move prep
4.00h testing
3.50h speed
1.50h barbara spellcheck issue
0.50h installation report dates
0.25h plan change issue
1.00h planning/discussion
0.50h speed issue
0.25h tina quote low-e layout
0.50h tina title 24 issue
0.25h kcrw:admin:contract update:unbilled
--------------------
1
17.75h
*** parsing: canonicalise account names to be case-insensitive ?
*** parsing: end directive may also be spelled end account; or end ends last directive
*** parsing: enforce positive price amounts, like ledger
eg 1€ @@ $-2 is not allowed
*** parsing: ignore/support all ledger assert directives ?
; Assertion directives Options
; These can occur in many places:
;
; ; Within an automated transaction, the assert is evaluated every time
; ; a posting is matched, with the expression context set to the
; ; matched posting.
; = /Food/
; assert account("Expenses:Food").total >= $100
; 2010-06-12 Sample
; Expenses:Food $100
; Assets:Checking
;
; ; At file scope, the expression is evaluated within "global" scope.
; assert account("Expenses:Food").total == $100
;
; ; At the top of a transction, the assertion's scope is the
; ; transaction. After a posting, the scope is that posting. Note
; ; however that account totals are only adjusted after successful
; ; parsing of a transaction, which means that all the assertions below
; ; are true, even though it appears as though the first posting should
; ; affect the total immediately, which is not the case.
; 2010-06-12 Sample 2
; assert account("Expenses:Food").total == $100
; Expenses:Food $50
; assert account("Expenses:Food").total == $100
; Assets:Checking
; assert account("Expenses:Food").total == $100
*** parsing: more date syntax ? last nov, next friday, optional this, week of
*** parsing: more flexible file including
currently only journals (not timelog files) can include, and only another journal
*** parsing: more period syntax ? every N days, biweekly
*** parsing: parse YNAB export
$ tail -n +2 YNAB/Exports/personal-Register.csv | hledger -f- print --rules-file YNAB/Exports/personal-Register.rules
using conversion rules file /Users/simon/personal/YNAB/Exports/personal-Register.rules
hledger: using amount-in-field and amount-out-field, found a value in both fiel
ds: ("$0.00","$4.68")
*** parsing: per-posting effective/actual dates
*** parsing: period expressions should allow interval at the end
eg support -p 'from 1/1 to 2/1 weekly'
*** parsing: safety check that effective date > actual (to catch eg 2009/12/30=1/4)
*** parsing: support apostrophe digit group separator
*** print: shouldn't support -M and --depth ?
*** register: for the love of god, show full descriptions
*** web: api
*** web: auto-complete accounts & amount as well as description
*** web: auto-complete from substrings, not just prefixes
*** web: better web ui/gui
*** web: completes one account name component in add form account fields
*** web: how to find out net worth, /register?q=assets+liabilities shows nothing
*** web: how to find out total spent in an account during a specific month
*** web: in-place editing
**** http://stackoverflow.com/questions/640971/setfocus-to-textbox-from-javascript-after-just-creating-the-textbox-with-javascr
*** web: lose io-storage
*** web: should display virtual postings with () or []
*** web: should take port from base-url when appropriate
*** web: update/remove browser startup
*** web: wai-handler-webkit, wai-handler-launch
*** account types & metadata
; chart of accounts
; defines allowed account names, hierarchy, default sort order,
; and some metadata (account numbers, cf http://www.netmba.com/accounting/fin/accounts/chart/)
ACCOUNTS
assets ; :number: 1000
cash ; :number: 1010
HT7 ; :number: 1011
jan ; :number: 1011.01
feb ; :number: 1011.02
mar ; :number: 1011.03
RSG ; :number: 1012
jan ; :number: 1012.01
feb ; :number: 1012.02
mar ; :number: 1012.03
bank ; :number: 1020
HT7 ; :number: 1021
RSG ; :number: 1022
reserve ; :number: 1023
liabilities ; :number: 2000
accounts payable ; :number: 2010
BSG/GI/RSG ; :number: 2011
equity ; :number: 3000
opening balances ; :number: 3010
income ; :number: 4000
HT7 ; :number: 4010
RSG ; :number: 4020
expenses ; :number: 6000
rent ; :number: 6010
or: http://furius.ca/beancount/examples/demo.ledger
2011-09-28 04:32:56 +04:00
2012-11-20 04:28:05 +04:00
*** anonymisation
**** payees
**** account names
**** amounts
**** dates
2012-02-23 19:28:36 +04:00
2012-11-20 04:28:05 +04:00
*** balance setting
ledger: You can accomplish "setting to the bank's view" with a transaction like this:
2012-02-23 19:28:36 +04:00
2012-11-20 04:28:05 +04:00
2011-08-12 Sample
Assets:Checking = $200.00
Equity:Adjustments
2012-02-23 19:28:36 +04:00
2012-11-20 04:28:05 +04:00
This tells Ledger (Git/3.0) that your checking account's balance must be $200
after this transaction is completed. It will put whatever amounts are
required to accomplish this into the Equity:Adjustments account.
2012-02-23 19:28:36 +04:00
2012-11-20 04:28:05 +04:00
what about balance assertions ?
2012-02-23 19:28:36 +04:00
2012-11-20 04:28:05 +04:00
*** clear, documented interfaces
*** client-side ui
*** debug logging
*** detect .hs plugins
*** don't recompile between autotest & functest
*** Double -> Decimal
*** query by commodity
*** generalise rules file ?
**** make it applicable to all formats ?
**** absorb its directives into journal format ?
*** i18n
*** journalAddTransaction should check txn balances
*** make generatejournal.hs script a sub-command
*** measure bug open times
http://code.google.com/p/support/wiki/IssueTrackerAPI
http://code.google.com/p/support/wiki/IssueTrackerAPIReference
*** more powerful storage layer
**** Clint's filestore_proof_of_concept.dpatch
2012-11-20 04:28:05 +04:00
New patches:
2012-11-20 04:28:05 +04:00
[filestore-proof-of-concept
Clint Adams <clint@softwarefreedom.org>**20110901172739
Ignore-this: 1991477c2b70d276665c52478dc54d3d
This is a somewhat broken replacement of the traditional file
storage with a forced darcs repo. It assumes that the
darcs repo already exists since Data.FileStore refuses to
initialize a repository in an extant directory. It does not
handle any error conditions well.
] hunk ./hledger-lib/Hledger/Read.hs 104
when (not exists) $ do
hPrintf stderr "No journal file \"%s\", creating it.\n" f
hPrintf stderr "Edit this file or use \"hledger add\" or \"hledger web\" to add transactions.\n"
- emptyJournal >>= writeFile f
+ emptyJournal >>= writeFileWithBackup f
-- | Give the content for a new auto-created journal file.
emptyJournal :: IO String
hunk ./hledger-lib/Hledger/Utils.hs 40
import Text.ParserCombinators.Parsec
import Text.Printf
import Text.RegexPR
+import System.FilePath (takeFileName, takeDirectory)
+import qualified Data.FileStore.Types as DFT
+import qualified Data.FileStore.Generic as DFG
+import Data.FileStore.Darcs (darcsFileStore)
-- import qualified Data.Map as Map
--
-- import Prelude hiding (readFile,writeFile,getContents,putStr,putStrLn)
hunk ./hledger-lib/Hledger/Utils.hs 432
-- | Apply a function the specified number of times. Possibly uses O(n) stack ?
applyN :: Int -> (a -> a) -> a -> a
applyN n f = (!! n) . iterate f
+
+-- Store file in VCS; Data.FileStore takes care of only committing
+-- when necessary.
+
+filestoreSave :: FilePath -> String -> IO ()
+filestoreSave f t = DFT.save assumedRepo assumedFilename assumedAuthor logMessage t
+ where
+ assumedRepo = darcsFileStore (takeDirectory f)
+ assumedFilename = takeFileName f
+ assumedAuthor = (DFT.Author "Hledger Role" "hledger@fake")
+ logMessage = "Some kind of change committed by some part of the hledger suite"
+
+writeFileWithBackup :: FilePath -> String -> IO ()
+writeFileWithBackup = filestoreSave
+
+-- modify existing file in filestore
+filestoreModify :: FilePath -> DFT.RevisionId -> String -> IO (Either DFT.MergeInfo ())
+filestoreModify f lr t = DFG.modify assumedRepo assumedFilename lr assumedAuthor logMessage t
+ where
+ assumedRepo = darcsFileStore (takeDirectory f)
+ assumedFilename = takeFileName f
+ assumedAuthor = (DFT.Author "Hledger Role" "hledger@fake")
+ logMessage = "Some kind of change committed by some part of the hledger suite"
+
+filestoreAppend :: FilePath -> String -> IO ()
+filestoreAppend f t = do
+ lastrev <- DFT.latest assumedRepo assumedFilename
+ oldcontents <- DFT.retrieve assumedRepo assumedFilename (Just lastrev)
+ result <- filestoreModify f lastrev (oldcontents ++ "\n\n" ++ t)
+ either (\x -> putStrLn "Help, the append didn't work and I am failing miserably.") (\x -> return ()) result
+ where
+ assumedRepo = darcsFileStore (takeDirectory f)
+ assumedFilename = takeFileName f
hunk ./hledger-lib/hledger-lib.cabal 60
,containers
,directory
,filepath
+ ,filestore
,mtl
,old-locale
,old-time
hunk ./hledger/Hledger/Cli/Add.hs 31
import qualified Data.Set as Set
import Hledger
-import Prelude hiding (putStr, putStrLn, appendFile)
-import Hledger.Utils.UTF8 (putStr, putStrLn, appendFile)
+import Prelude hiding (putStr, putStrLn)
+import Hledger.Utils.UTF8 (putStr, putStrLn)
import Hledger.Cli.Options
import Hledger.Cli.Register (postingsReportAsText)
import Hledger.Cli.Utils
hunk ./hledger/Hledger/Cli/Add.hs 194
journalAddTransaction :: Journal -> CliOpts -> Transaction -> IO Journal
journalAddTransaction j@Journal{jtxns=ts} opts t = do
let f = journalFilePath j
- appendToJournalFile f $ showTransaction t
+ filestoreAppend f $ showTransaction t
when (debug_ opts) $ do
putStrLn $ printf "\nAdded transaction to %s:" f
putStrLn =<< registerFromString (show t)
hunk ./hledger/Hledger/Cli/Add.hs 200
return j{jtxns=ts++[t]}
--- | Append data to a journal file; or if the file is "-", dump it to stdout.
-appendToJournalFile :: FilePath -> String -> IO ()
-appendToJournalFile f s =
- if f == "-"
- then putStr $ sep ++ s
- else appendFile f $ sep++s
- where
- -- appendFile means we don't need file locking to be
- -- multi-user-safe, but also that we can't figure out the minimal
- -- number of newlines needed as separator
- sep = "\n\n"
- -- sep | null $ strip t = ""
- -- | otherwise = replicate (2 - min 2 (length lastnls)) '\n'
- -- where lastnls = takeWhile (=='\n') $ reverse t
-
-- | Convert a string of journal data into a register report.
registerFromString :: String -> IO String
registerFromString s = do
hunk ./hledger/Hledger/Cli/Utils.hs 18
journalSpecifiedFileIsNewer,
fileModificationTime,
openBrowserOn,
- writeFileWithBackup,
- writeFileWithBackupIfChanged,
readFileStrictly,
Test(TestList),
)
hunk ./hledger/Hledger/Cli/Utils.hs 25
import Control.Exception
import Data.List
import Data.Maybe
-import Safe (readMay)
import System.Console.CmdArgs
hunk ./hledger/Hledger/Cli/Utils.hs 26
-import System.Directory (getModificationTime, getDirectoryContents, copyFile)
+import System.Directory (getModificationTime)
import System.Exit
hunk ./hledger/Hledger/Cli/Utils.hs 28
-import System.FilePath ((</>), splitFileName, takeDirectory)
import System.Info (os)
import System.Process (readProcessWithExitCode)
import System.Time (ClockTime, getClockTime, diffClockTimes, TimeDiff(TimeDiff))
hunk ./hledger/Hledger/Cli/Utils.hs 123
-- what not.
-- ::ShellExecute(NULL, "open", "www.somepage.com", NULL, NULL, SW_SHOWNORMAL);
--- | Back up this file with a (incrementing) numbered suffix then
--- overwrite it with this new text, or give an error, but only if the text
--- is different from the current file contents, and return a flag
--- indicating whether we did anything.
-writeFileWithBackupIfChanged :: FilePath -> String -> IO Bool
-writeFileWithBackupIfChanged f t = do
- s <- readFile f
- if t == s then return False
- else backUpFile f >> writeFile f t >> return True
-
--- | Back up this file with a (incrementing) numbered suffix, then
--- overwrite it with this new text, or give an error.
-writeFileWithBackup :: FilePath -> String -> IO ()
-writeFileWithBackup f t = backUpFile f >> writeFile f t
-
readFileStrictly :: FilePath -> IO String
readFileStrictly f = readFile f >>= \s -> Control.Exception.evaluate (length s) >> return s
hunk ./hledger/Hledger/Cli/Utils.hs 125
-
--- | Back up this file with a (incrementing) numbered suffix, or give an error.
-backUpFile :: FilePath -> IO ()
-backUpFile fp = do
- fs <- safeGetDirectoryContents $ takeDirectory $ fp
- let (d,f) = splitFileName fp
- versions = catMaybes $ map (f `backupNumber`) fs
- next = maximum (0:versions) + 1
- f' = printf "%s.%d" f next
- copyFile fp (d </> f')
-
-safeGetDirectoryContents :: FilePath -> IO [FilePath]
-safeGetDirectoryContents "" = getDirectoryContents "."
-safeGetDirectoryContents fp = getDirectoryContents fp
-
--- | Does the second file represent a backup of the first, and if so which version is it ?
-backupNumber :: FilePath -> FilePath -> Maybe Int
-backupNumber f g = case regexMatch ("^" ++ f ++ "\\.([0-9]+)$") g of
- Just (_, ((_,suffix):_)) -> readMay suffix
- _ -> Nothing
2012-11-20 04:28:05 +04:00
*** nice standard financial reports
*** 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
2012-02-23 19:28:36 +04:00
2012-11-20 04:28:05 +04:00
*** runtime report templates
*** separate --display and --limit expressions, eg for --depth
*** perf: more speed, memory optimisation
*** support - in period expressions
*** support -V ?
*** talkback feature
gather data on real-world installation & usage issues
simplify bug reporting/handling
improve reliability
*** upload feature