diff --git a/NOTES b/NOTES
index 27fdbc511..e0a517263 100644
--- a/NOTES
+++ b/NOTES
@@ -27,7 +27,6 @@ ABILITY TO SEE. --Scott McCloud, Understanding Comics
** less is more
** code review/pair programming
-
* todo/backlog
** documentation, marketing
*** developer notes & log
@@ -62,7 +61,6 @@ hledger helps you track and understand your finances, making calculations based
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.
-
*** website
**** add tables of contents
**** integrate binaries, demo, pastebin
@@ -176,6 +174,11 @@ hledger.org/{list,bugs}/* also works
****** testing
****** coding
****** funding process
+******* donation blurb
+If you like project 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
@@ -404,6 +407,54 @@ competitors/fellow niche inhabitants
**** home edition
**** real-time project ledger
+**** in-place transaction editing fund drive
+
+Fund drive: hledger-web in-place transaction editing
+
+Goal: I would like to raise $X or more to fund basic in-place
+transaction editing for hledger-web. hledger-web is a web-based GUI for
+hledger (and ledger), which are free/open-source accounting programs
+providing a lean and efficient alternative to quicken, gnucash, mint.com
+etc.
+
+Current hledger-web[1] has simple web forms for adding transactions and
+for editing the whole journal, but there is no easy ui for editing a
+single existing transaction. Such a ui is an important step towards making
+hledger (and ledger) usable by non techies, which would greatly expand
+these tools' applicability and potential user/contributor base.
+
+Plan: do the front-end javascript and backend haskell work required to
+support:
+
+- click date, description, account or amount cells in a register view to make that cell editable
+- tab moves to the next cell
+- enter or click on save button updates the transaction in the journal, overwriting/rewriting the whole file
+- tested in firefox/chrome/safari
+
+The proposed amount will fund about 10 hours of work, so the above
+features must be implemented very expeditiously. Other improvements will
+be tackled in a followup fund drive if this one succeeds (or in this one
+if the funding goal is exceeded.) Those future items include:
+
+- history/content awareness, smart defaults and auto-completion wherever useful
+- date picker widget
+- ability to add/remove postings
+- ability to edit metadata/tags
+- ability to edit other transaction/posting fields
+- ledger compatibility
+- compatibility testing/fixes for all the major browsers
+- edit conflict checking - don't overwrite concurrent external edits
+- try harder to preserve existing file layout/co-exist better with external edits
+- a similar ui for adding new transactions
+- pleasant visual style
+
+Also, 10% of the amount raised will be tithed to three contributing
+projects or developers (ledger and two others of my choice.)
+
+This project will go forward if
+
+[1] http://demo.hledger.org:5001
+
** packaging, installability
*** linux
***** debian/ubuntu packaging
@@ -428,6 +479,48 @@ competitors/fellow niche inhabitants
***** test all ledger file format features
***** clarify hledgerisms in file format - that hledger can read but ledger can't
**** ledger 3 baseline tests
+**** MaybeSo subtotal rounding issue
+I had a question about balance totals. Given this test data:
+
+$ cat test.dat
+D $1,000.00
+P 2011-01-01 22:00:00-0800 TESTA $78.35
+P 2011-01-01 22:00:00-0800 TESTB $15.86
+P 2011-01-01 22:00:09-0800 TESTC $13.01
+
+2011/01/01 Example
+ Assets:Brokerage:TESTA 188.424 TESTA @ $76.61
+ Assets:Brokerage:TESTB 1,809.282 TESTB @ $15.60
+ Assets:Brokerage:TESTC 384.320 TESTC @@ $5,000.00
+ Assets:Brokerage:TESTC 5.306 TESTC @@ $68.18
+ Equity:Opening Balances
+
+I'm a little bit surprised that the sub-accounts
+reflect a difference from the top level account
+w/re to rounding the last cent:
+
+$ ledger -V -f test.dat bal
+ $48,527.27 Assets:Brokerage
+ $14,763.02 TESTA
+ $28,695.21 TESTB
+ $5,069.03 TESTC
+ $-47,728.14 Equity:Opening Balances
+--------------------
+ $799.13
+
+Even if --no-rounding is passed in:
+
+$ ledger -V -f test.dat --no-rounding bal
+ $48,527.27 Assets:Brokerage
+ $14,763.02 TESTA
+ $28,695.21 TESTB
+ $5,069.03 TESTC
+ $-47,728.14 Equity:Opening Balances
+--------------------
+ $799.13
+
+Is there something off with how the data aboce is set up? Should I be
+using be more place holders?
*** performance
**** speed, benchmark tests
**** memory usage
@@ -450,14 +543,12 @@ competitors/fellow niche inhabitants
**** usability
**** download & usage stats
** errors
-*** convert: unparseable example from Cliff:
-03/11/11,,"CK# 9988",-$25.01,+$100.00
-03/12/11,,"DEPOSIT - BRANCH",+$50.00,+$150.00
-
*** about:
The quick bug list - predates the web-based bug tracker, still in use as
front-line tracker by the org-mode-comfortable hledger developer(s)
+*** parsing: timezone should be supported in historical price records, elsewhere ? (greenskeleton)
+https://gist.github.com/972281
*** tools: avoid haskell compiles and compile errors while setting up makefile vars
joyful$ make help
GHCi runtime linker: fatal error: I found a duplicate definition for symbol
@@ -499,8 +590,6 @@ could not balance this transaction (real postings are off by $-4,931.82)
Assets:Brokerage:TESTC 5.306 TESTC @@ $68.18
Equity:Opening Balances
-*** parsing: timezone should be supported in historical price records, elsewhere ? (greenskeleton)
-https://gist.github.com/972281
*** parsing: recursive file includes cause a hang
echo "!include rec" > rec
hledger -f rec print
@@ -550,6 +639,1122 @@ http://www.burningcutlery.com/derek/winsetup/
http://msdn.microsoft.com/en-us/library/ms714415(v=VS.85).aspx
*** auto-creating missing journal files - annoying or helpful ?
** refactoring, cleanup
+*** optionsgeddon oh my god
+**** old help
+Usage: hledger [OPTIONS] COMMAND [PATTERNS]
+ hledger [OPTIONS] convert CSVFILE
+
+Reads your ~/.journal file, or another specified by $LEDGER or -f, and
+runs the specified command (may be abbreviated):
+
+ add - prompt for new transactions and add them to the journal
+ balance - show accounts, with balances
+ convert - show the specified CSV file as a hledger journal
+ histogram - show a barchart of transactions per day or other interval
+ print - show transactions in journal format
+ register - show transactions as a register with running balance
+ stats - show various statistics for a journal
+ test - run self-tests
+
+hledger options:
+ -f FILE --file=FILE use a different journal/timelog file; - means stdin
+ --no-new-accounts don't allow to create new accounts
+ -b DATE --begin=DATE report on transactions on or after this date
+ -e DATE --end=DATE report on transactions before this date
+ -p EXPR --period=EXPR report on transactions during the specified period
+ and/or with the specified reporting interval
+ -C --cleared report only on cleared transactions
+ -U --uncleared report only on uncleared transactions
+ -B --cost, --basis report cost of commodities
+ --depth=N hide accounts/transactions deeper than this
+ -d EXPR --display=EXPR show only transactions matching EXPR (where
+ EXPR is 'dOP[DATE]' and OP is <, <=, =, >=, >)
+ --effective use transactions' effective dates, if any
+ -E --empty show empty/zero things which are normally elided
+ -R --real report only on real (non-virtual) transactions
+ --flat balance: show full account names, unindented
+ --drop=N balance: with --flat, elide first N account name components
+ --no-total balance: hide the final total
+ -D --daily register, stats: report by day
+ -W --weekly register, stats: report by week
+ -M --monthly register, stats: report by month
+ -Q --quarterly register, stats: report by quarter
+ -Y --yearly register, stats: report by year
+ -v --verbose show more verbose output
+ --debug show extra debug output; implies verbose
+ --binary-filename show the download filename for this hledger build
+ -V --version show version information
+ -h --help show command-line usage
+
+DATES can be y/m/d or smart dates like "last month". PATTERNS are regular
+expressions which filter by account name. Prefix a pattern with desc: to
+filter by transaction description instead, prefix with not: to negate it.
+When using both, not: comes last.
+
+**** implementations
+***** original getopts
+progname_cli = "hledger"
+
+-- | The program name which, if we are invoked as (via symlink or
+-- renaming), causes us to default to reading the user's time log instead
+-- of their journal.
+progname_cli_time = "hours"
+
+usage_preamble_cli =
+ "Usage: hledger [OPTIONS] COMMAND [PATTERNS]\n" ++
+ " hledger [OPTIONS] convert CSVFILE\n" ++
+ "\n" ++
+ "Reads your ~/.journal file, or another specified by $LEDGER or -f, and\n" ++
+ "runs the specified command (may be abbreviated):\n" ++
+ "\n" ++
+ " add - prompt for new transactions and add them to the journal\n" ++
+ " balance - show accounts, with balances\n" ++
+ " convert - show the specified CSV file as a hledger journal\n" ++
+ " histogram - show a barchart of transactions per day or other interval\n" ++
+ " print - show transactions in journal format\n" ++
+ " register - show transactions as a register with running balance\n" ++
+ " stats - show various statistics for a journal\n" ++
+ " test - run self-tests\n" ++
+ "\n"
+
+usage_options_cli = usageInfo "hledger options:" options_cli
+
+usage_postscript_cli =
+ "\n" ++
+ "DATES can be y/m/d or smart dates like \"last month\". PATTERNS are regular\n" ++
+ "expressions which filter by account name. Prefix a pattern with desc: to\n" ++
+ "filter by transaction description instead, prefix with not: to negate it.\n" ++
+ "When using both, not: comes last.\n"
+
+usage_cli = concat [
+ usage_preamble_cli
+ ,usage_options_cli
+ ,usage_postscript_cli
+ ]
+
+-- | Command-line options we accept.
+options_cli :: [OptDescr Opt]
+options_cli = [
+ Option "f" ["file"] (ReqArg File "FILE") "use a different journal/timelog file; - means stdin"
+ ,Option "b" ["begin"] (ReqArg Begin "DATE") "report on transactions on or after this date"
+ ,Option "e" ["end"] (ReqArg End "DATE") "report on transactions before this date"
+ ,Option "p" ["period"] (ReqArg Period "EXPR") ("report on transactions during the specified period\n" ++
+ "and/or with the specified reporting interval\n")
+ ,Option "C" ["cleared"] (NoArg Cleared) "report only on cleared transactions"
+ ,Option "U" ["uncleared"] (NoArg UnCleared) "report only on uncleared transactions"
+ ,Option "B" ["cost","basis"] (NoArg CostBasis) "report cost of commodities"
+ ,Option "" ["alias"] (ReqArg Alias "ACCT=ALIAS") "display ACCT's name as ALIAS in reports"
+ ,Option "" ["depth"] (ReqArg Depth "N") "hide accounts/transactions deeper than this"
+ ,Option "d" ["display"] (ReqArg Display "EXPR") ("show only transactions matching EXPR (where\n" ++
+ "EXPR is 'dOP[DATE]' and OP is <, <=, =, >=, >)")
+ ,Option "" ["effective"] (NoArg Effective) "use transactions' effective dates, if any"
+ ,Option "E" ["empty"] (NoArg Empty) "show empty/zero things which are normally elided"
+ ,Option "" ["no-elide"] (NoArg NoElide) "no eliding at all, stronger than -E (eg for balance report)"
+ ,Option "R" ["real"] (NoArg Real) "report only on real (non-virtual) transactions"
+ ,Option "" ["flat"] (NoArg Flat) "balance: show full account names, unindented"
+ ,Option "" ["drop"] (ReqArg Drop "N") "balance: with --flat, elide first N account name components"
+ ,Option "" ["no-total"] (NoArg NoTotal) "balance: hide the final total"
+ ,Option "D" ["daily"] (NoArg DailyOpt) "register, stats: report by day"
+ ,Option "W" ["weekly"] (NoArg WeeklyOpt) "register, stats: report by week"
+ ,Option "M" ["monthly"] (NoArg MonthlyOpt) "register, stats: report by month"
+ ,Option "Q" ["quarterly"] (NoArg QuarterlyOpt) "register, stats: report by quarter"
+ ,Option "Y" ["yearly"] (NoArg YearlyOpt) "register, stats: report by year"
+ ,Option "" ["no-new-accounts"] (NoArg NoNewAccts) "add: don't allow creating new accounts"
+ ,Option "r" ["rules"] (ReqArg RulesFile "FILE") "convert: rules file to use (default:JOURNAL.rules)"
+ ,Option "F" ["format"] (ReqArg ReportFormat "STR") "use STR as the format"
+ ,Option "v" ["verbose"] (NoArg Verbose) "show more verbose output"
+ ,Option "" ["debug"] (NoArg Debug) "show extra debug output; implies verbose"
+ ,Option "" ["binary-filename"] (NoArg BinaryFilename) "show the download filename for this hledger build"
+ ,Option "V" ["version"] (NoArg Version) "show version information"
+ ,Option "h" ["help"] (NoArg Help) "show command-line usage"
+ ]
+
+-- | An option value from a command-line flag.
+data Opt =
+ File {value::String}
+ | NoNewAccts
+ | Begin {value::String}
+ | End {value::String}
+ | Period {value::String}
+ | Cleared
+ | UnCleared
+ | CostBasis
+ | Alias {value::String}
+ | Depth {value::String}
+ | Display {value::String}
+ | Effective
+ | Empty
+ | NoElide
+ | Real
+ | Flat
+ | Drop {value::String}
+ | NoTotal
+ | DailyOpt
+ | WeeklyOpt
+ | MonthlyOpt
+ | QuarterlyOpt
+ | YearlyOpt
+ | RulesFile {value::String}
+ | ReportFormat {value::String}
+ | Help
+ | Verbose
+ | Version
+ | BinaryFilename
+ | Debug
+ -- XXX add-on options, must be defined here for now
+ -- vty
+ | DebugVty
+ -- web
+ | BaseUrl {value::String}
+ | Port {value::String}
+ -- chart
+ | ChartOutput {value::String}
+ | ChartItems {value::String}
+ | ChartSize {value::String}
+ deriving (Show,Eq)
+
+-- these make me nervous
+optsWithConstructor f opts = concatMap get opts
+ where get o = [o | f v == o] where v = value o
+
+optsWithConstructors fs opts = concatMap get opts
+ where get o = [o | any (== o) fs]
+
+optValuesForConstructor f opts = concatMap get opts
+ where get o = [v | f v == o] where v = value o
+
+optValuesForConstructors fs opts = concatMap get opts
+ where get o = [v | any (\f -> f v == o) fs] where v = value o
+
+-- | Parse the command-line arguments into options and arguments using the
+-- specified option descriptors. Any smart dates in the options are
+-- converted to explicit YYYY/MM/DD format based on the current time. If
+-- parsing fails, raise an error, displaying the problem along with the
+-- provided usage string.
+parseArgumentsWith :: [OptDescr Opt] -> IO ([Opt], [String])
+parseArgumentsWith options = do
+ rawargs <- map fromPlatformString `fmap` getArgs
+ parseArgumentsWith' options rawargs
+
+parseArgumentsWith' options rawargs = do
+ let (opts,args,errs) = getOpt Permute options rawargs
+ opts' <- fixOptDates opts
+ let opts'' = if Debug `elem` opts' then Verbose:opts' else opts'
+ if null errs
+ then return (opts'',args)
+ else argsError (concat errs) >> return ([],[])
+
+argsError :: String -> IO ()
+argsError = ioError . userError' . (++ " Run with --help to see usage.")
+
+-- | Convert any fuzzy dates within these option values to explicit ones,
+-- based on today's date.
+fixOptDates :: [Opt] -> IO [Opt]
+fixOptDates opts = do
+ d <- getCurrentDay
+ return $ map (fixopt d) opts
+ where
+ fixopt d (Begin s) = Begin $ fixSmartDateStr d s
+ fixopt d (End s) = End $ fixSmartDateStr d s
+ fixopt d (Display s) = -- hacky
+ Display $ regexReplaceBy "\\[.+?\\]" fixbracketeddatestr s
+ where fixbracketeddatestr s = "[" ++ fixSmartDateStr d (init $ tail s) ++ "]"
+ fixopt _ o = o
+
+-- | Figure out the overall date span we should report on, based on any
+-- begin/end/period options provided. If there is a period option, the
+-- others are ignored.
+dateSpanFromOpts :: Day -> [Opt] -> DateSpan
+dateSpanFromOpts refdate opts
+ | not (null popts) = case parsePeriodExpr refdate $ last popts of
+ Right (_, s) -> s
+ Left e -> parseerror e
+ | otherwise = DateSpan lastb laste
+ where
+ popts = optValuesForConstructor Period opts
+ bopts = optValuesForConstructor Begin opts
+ eopts = optValuesForConstructor End opts
+ lastb = listtomaybeday bopts
+ laste = listtomaybeday eopts
+ listtomaybeday vs = if null vs then Nothing else Just $ parse $ last vs
+ where parse = parsedate . fixSmartDateStr refdate
+
+-- | Figure out the reporting interval, if any, specified by the options.
+-- If there is a period option, the others are ignored.
+intervalFromOpts :: [Opt] -> Interval
+intervalFromOpts opts =
+ case (periodopts, intervalopts) of
+ ((p:_), _) -> case parsePeriodExpr (parsedate "0001/01/01") p of
+ Right (i, _) -> i
+ Left e -> parseerror e
+ (_, (DailyOpt:_)) -> Days 1
+ (_, (WeeklyOpt:_)) -> Weeks 1
+ (_, (MonthlyOpt:_)) -> Months 1
+ (_, (QuarterlyOpt:_)) -> Quarters 1
+ (_, (YearlyOpt:_)) -> Years 1
+ (_, _) -> NoInterval
+ where
+ periodopts = reverse $ optValuesForConstructor Period opts
+ intervalopts = reverse $ filter (`elem` [DailyOpt,WeeklyOpt,MonthlyOpt,QuarterlyOpt,YearlyOpt]) opts
+
+rulesFileFromOpts :: [Opt] -> Maybe FilePath
+rulesFileFromOpts opts = listtomaybe $ optValuesForConstructor RulesFile opts
+ where
+ listtomaybe [] = Nothing
+ listtomaybe vs = Just $ head vs
+
+-- | Default balance format string: "%20(total) %2(depth_spacer)%-(account)"
+defaultBalanceFormatString :: [FormatString]
+defaultBalanceFormatString = [
+ FormatField False (Just 20) Nothing Total
+ , FormatLiteral " "
+ , FormatField True (Just 2) Nothing DepthSpacer
+ , FormatField True Nothing Nothing Format.Account
+ ]
+
+-- | Parses the --format string to either an error message or a format string.
+parseFormatFromOpts :: [Opt] -> Either String [FormatString]
+parseFormatFromOpts opts = listtomaybe $ optValuesForConstructor ReportFormat opts
+ where
+ listtomaybe :: [String] -> Either String [FormatString]
+ listtomaybe [] = Right defaultBalanceFormatString
+ listtomaybe vs = parseFormatString $ head vs
+
+-- | Returns the format string. If the string can't be parsed it fails with error'.
+formatFromOpts :: [Opt] -> [FormatString]
+formatFromOpts opts = case parseFormatFromOpts opts of
+ Left err -> error' err
+ Right format -> format
+
+-- | Get the value of the (last) depth option, if any.
+depthFromOpts :: [Opt] -> Maybe Int
+depthFromOpts opts = listtomaybeint $ optValuesForConstructor Depth opts
+ where
+ listtomaybeint [] = Nothing
+ listtomaybeint vs = Just $ read $ last vs
+
+-- | Get the value of the (last) drop option, if any, otherwise 0.
+dropFromOpts :: [Opt] -> Int
+dropFromOpts opts = fromMaybe 0 $ listtomaybeint $ optValuesForConstructor Drop opts
+ where
+ listtomaybeint [] = Nothing
+ listtomaybeint vs = Just $ read $ last vs
+
+-- | Get the value of the (last) display option, if any.
+displayExprFromOpts :: [Opt] -> Maybe String
+displayExprFromOpts opts = listtomaybe $ optValuesForConstructor Display opts
+ where
+ listtomaybe [] = Nothing
+ listtomaybe vs = Just $ last vs
+
+-- | Get the value of the (last) baseurl option, if any.
+baseUrlFromOpts :: [Opt] -> Maybe String
+baseUrlFromOpts opts = listtomaybe $ optValuesForConstructor BaseUrl opts
+ where
+ listtomaybe [] = Nothing
+ listtomaybe vs = Just $ last vs
+
+-- | Get the value of the (last) port option, if any.
+portFromOpts :: [Opt] -> Maybe Int
+portFromOpts opts = listtomaybeint $ optValuesForConstructor Port opts
+ where
+ listtomaybeint [] = Nothing
+ listtomaybeint vs = Just $ read $ last vs
+
+
+-- | Get a maybe boolean representing the last cleared/uncleared option if any.
+clearedValueFromOpts opts | null os = Nothing
+ | last os == Cleared = Just True
+ | otherwise = Just False
+ where os = optsWithConstructors [Cleared,UnCleared] opts
+
+-- | Detect which date we will report on, based on --effective.
+whichDateFromOpts :: [Opt] -> WhichDate
+whichDateFromOpts opts = if Effective `elem` opts then EffectiveDate else ActualDate
+
+-- | Were we invoked as \"hours\" ?
+usingTimeProgramName :: IO Bool
+usingTimeProgramName = do
+ progname <- getProgName
+ return $ map toLower progname == progname_cli_time
+
+-- | Get the journal file path from options, an environment variable, or a default
+journalFilePathFromOpts :: [Opt] -> IO String
+journalFilePathFromOpts opts = do
+ istimequery <- usingTimeProgramName
+ f <- if istimequery then myTimelogPath else myJournalPath
+ return $ last $ f : optValuesForConstructor File opts
+
+aliasesFromOpts :: [Opt] -> [(AccountName,AccountName)]
+aliasesFromOpts opts = map parseAlias $ optValuesForConstructor Alias opts
+ where
+ -- similar to ledgerAlias
+ parseAlias :: String -> (AccountName,AccountName)
+ parseAlias s = (accountNameWithoutPostingType $ strip orig
+ ,accountNameWithoutPostingType $ strip alias')
+ where
+ (orig, alias) = break (=='=') s
+ alias' = case alias of ('=':rest) -> rest
+ _ -> orig
+
+-- | Gather filter pattern arguments into a list of account patterns and a
+-- list of description patterns. We interpret pattern arguments as
+-- follows: those prefixed with "desc:" are description patterns, all
+-- others are account patterns; also patterns prefixed with "not:" are
+-- negated. not: should come after desc: if both are used.
+parsePatternArgs :: [String] -> ([String],[String])
+parsePatternArgs args = (as, ds')
+ where
+ descprefix = "desc:"
+ (ds, as) = partition (descprefix `isPrefixOf`) args
+ ds' = map (drop (length descprefix)) ds
+
+-- | Convert application options to the library's generic filter specification.
+optsToFilterSpec :: [Opt] -> [String] -> Day -> FilterSpec
+optsToFilterSpec opts args d = FilterSpec {
+ datespan=dateSpanFromOpts d opts
+ ,cleared=clearedValueFromOpts opts
+ ,real=Real `elem` opts
+ ,empty=Empty `elem` opts
+ ,acctpats=apats
+ ,descpats=dpats
+ ,depth = depthFromOpts opts
+ }
+ where (apats,dpats) = parsePatternArgs args
+
+-- currentLocalTimeFromOpts opts = listtomaybe $ optValuesForConstructor CurrentLocalTime opts
+-- where
+-- listtomaybe [] = Nothing
+-- listtomaybe vs = Just $ last vs
+
+tests_Hledger_Cli_Options = TestList
+ [
+ "dateSpanFromOpts" ~: do
+ let todaysdate = parsedate "2008/11/26"
+ let gives = is . show . dateSpanFromOpts todaysdate
+ [] `gives` "DateSpan Nothing Nothing"
+ [Begin "2008", End "2009"] `gives` "DateSpan (Just 2008-01-01) (Just 2009-01-01)"
+ [Period "in 2008"] `gives` "DateSpan (Just 2008-01-01) (Just 2009-01-01)"
+ [Begin "2005", End "2007",Period "in 2008"] `gives` "DateSpan (Just 2008-01-01) (Just 2009-01-01)"
+
+ ,"intervalFromOpts" ~: do
+ let gives = is . intervalFromOpts
+ [] `gives` NoInterval
+ [DailyOpt] `gives` Days 1
+ [WeeklyOpt] `gives` Weeks 1
+ [MonthlyOpt] `gives` Months 1
+ [QuarterlyOpt] `gives` Quarters 1
+ [YearlyOpt] `gives` Years 1
+ [Period "weekly"] `gives` Weeks 1
+ [Period "monthly"] `gives` Months 1
+ [Period "quarterly"] `gives` Quarters 1
+ [WeeklyOpt, Period "yearly"] `gives` Years 1
+
+ ]
+
+***** cmdargs implicit ADT with ifdefs
+progname = "hledger"
+progversion = progversionstr progname
+
+progname_cli = progname
+
+-- | The program name which, if we are invoked as (via symlink or
+-- renaming), causes us to default to reading the user's time log instead
+-- of their journal.
+progname_cli_time = "hours"
+
+usage_preamble =
+ "Usage: hledger [OPTIONS] COMMAND [PATTERNS]\n" ++
+ " hledger [OPTIONS] convert CSVFILE\n" ++
+ "\n" ++
+ "Reads your ~/.journal file, or another specified by $LEDGER or -f, and\n" ++
+ "runs the specified command (may be abbreviated):\n" ++
+ "\n" ++
+ " add - prompt for new transactions and add them to the journal\n" ++
+ " balance - show accounts, with balances\n" ++
+ " convert - show the specified CSV file as a hledger journal\n" ++
+ " histogram - show a barchart of transactions per day or other interval\n" ++
+ " print - show transactions in journal format\n" ++
+ " register - show transactions as a register with running balance\n" ++
+ " stats - show various statistics for a journal\n" ++
+ " test - run self-tests\n" ++
+ "\n"
+
+usage_postscript = [
+ "DATES can be y/m/d or smart dates like \"last month\". PATTERNS are regular"
+ ,"expressions which filter by account name. Prefix a pattern with desc: to"
+ ,"filter by transaction description instead, prefix with not: to negate it."
+ ,"When using both, not: comes last."
+ ]
+
+-- | Command-line options & arguments we accept.
+-- data Opts =
+-- Add {
+-- } |
+-- Balance {
+-- }
+-- Convert {
+-- }
+-- Histogram {
+-- }
+-- Print {
+-- }
+-- Register {
+-- }
+-- Stats {
+-- }
+-- Test {
+-- }
+data Opts = Opts {
+ -- :: -- hledger options
+ file :: Maybe FilePath
+ -- :: -- hledger-lib options
+ ,begin :: Maybe String
+ ,end :: Maybe String
+ ,period :: Maybe String
+ ,cleared_ :: Bool
+ ,uncleared :: Bool
+ ,cost :: Bool
+ ,depth_ :: Maybe Int
+ ,display :: Maybe String
+ ,effective :: Bool
+ ,empty_ :: Bool
+ ,no_elide :: Bool
+ ,real_ :: Bool
+ ,flat :: Bool
+ ,drop_ :: Int
+ ,no_total :: Bool
+ ,daily :: Bool
+ ,weekly :: Bool
+ ,monthly :: Bool
+ ,quarterly :: Bool
+ ,yearly :: Bool
+ ,format :: Maybe String
+ -- :: -- hledger options
+ ,alias :: [String]
+ ,no_new_accounts :: Bool
+ ,rules_file :: Maybe String
+ ,binary_filename :: Bool
+ ,debug :: Bool
+
+ -- :: -- add-ons' options, must be defined here for now
+#ifdef HLEDGERVTY
+ ,debug_vty :: Bool
+#endif
+#ifdef HLEDGERWEB
+ ,base_url :: Maybe String
+ ,port :: Maybe Int
+#endif
+#ifdef HLEDGERCHART
+ ,chart_output :: Maybe String
+ ,chart_items :: Maybe Int
+ ,chart_size :: Maybe String
+#endif
+
+ ,args_ :: [String]
+
+ } deriving (Show, Data, Typeable)
+
+-- deriving instance Default Day
+-- instance Default DateSpan where def = nulldatespan
+-- instance Default Interval where def = NoInterval
+
+defopts = Opts {
+ -- = -- hledger options
+ file = def &= name "f" &= typFile &= help "use a different journal file; - means stdin"
+ -- = -- hledger-lib options
+ ,begin = def &= name "b" &= typ "DATE" &= help "report on transactions on or after this date"
+ ,end = def &= name "e" &= typ "DATE" &= help "report on transactions before this date"
+ ,period = def &= typ "PERIODEXPR" &= help "report on transactions during the specified period and/or with the specified reporting interval"
+ ,cleared_ = def &= name "c" &= help "report only on cleared transactions"
+ ,uncleared = def &= name "u" &= help "report only on uncleared transactions"
+ ,cost = def &= name "B" &= help "report cost of commodities"
+ ,depth_ = def &= typ "N" &= help "hide accounts/transactions deeper than this"
+ ,display = def &= typ "DISPLAYEXPR" &= name "d" &= help "show only transactions matching expr (where expr is 'dop[date]' and op is <, <=, =, >=, >)"
+ ,effective = def &= help "use transactions' effective dates, if any"
+ ,empty_ = def &= name "E" &= help "show empty/zero things which are normally elided"
+ ,no_elide = def &= help "no eliding at all, stronger than -e (eg for balance report)"
+ ,real_ = def &= name "r" &= help "report only on real (non-virtual) transactions"
+ ,flat = def &= help "balance: show full account names, unindented"
+ ,drop_ = def &= typ "N" &= help "balance: with --flat, omit this many leading account name components"
+ ,no_total = def &= help "balance: hide the final total"
+ ,daily = def &= name "D" &= help "register, stats: report by day"
+ ,weekly = def &= name "W" &= help "register, stats: report by week"
+ ,monthly = def &= name "M" &= help "register, stats: report by month"
+ ,quarterly = def &= name "Q" &= help "register, stats: report by quarter"
+ ,yearly = def &= name "Y" &= help "register, stats: report by year"
+ ,format = def &= typ "FORMATSTR" &= name "F" &= help "use this custom line format in reports"
+ -- = -- hledger options
+ ,alias = def &= typ "ACCT=ALIAS" &= help "display ACCT's name as ALIAS in reports"
+ ,no_new_accounts = def &= help "add: don't allow creating new accounts"
+ ,rules_file = def &= typFile &= help "convert: rules file to use (default: CSVFILE.rules)"
+ ,binary_filename = def &= help "show the download filename for this hledger build, and exit"
+ ,debug = def &= help "show extra debug output; implies verbose"
+ ,args_ = def &= args &= typ "COMMAND [PATTERNS]"
+ -- = -- add-ons' options, must be defined here for now
+#ifdef HLEDGERVTY
+ ,debug_vty = def
+#endif
+#ifdef HLEDGERWEB
+ ,base_url = def
+ ,port = def
+#endif
+#ifdef HLEDGERCHART
+ ,chart_output = def
+ ,chart_items = def
+ ,chart_size = def
+#endif
+ }
+ &= verbosity
+ &= program progname
+ &= summary progversion
+ &= details usage_postscript
+
+-- pre-decoding would be easier but doesn't work at least with ghc 6.12/cmdargs 0.7/unix:
+-- getArgsDecoded = map fromPlatformString `fmap` getArgs
+-- getHledgerOpts = getArgsDecoded >>= flip withArgs (cmdArgs defopts) >>= processOpts >>= checkOpts
+getHledgerOpts :: IO Opts
+getHledgerOpts = cmdArgs defopts >>= processOpts >>= checkOpts
+
+processOpts :: Opts -> IO Opts
+processOpts opts = do
+ let opts' = decodeOpts opts
+ fixMostOptDates opts'
+
+-- | Convert possibly encoded option values to regular unicode strings.
+decodeOpts :: Opts -> Opts
+decodeOpts opts@Opts{..} = opts {
+ file = maybe Nothing (Just . fromPlatformString) file
+ ,begin = maybe Nothing (Just . fromPlatformString) begin
+ ,end = maybe Nothing (Just . fromPlatformString) end
+ ,period = maybe Nothing (Just . fromPlatformString) period
+ ,display = maybe Nothing (Just . fromPlatformString) display
+ ,format = maybe Nothing (Just . fromPlatformString) format
+ ,alias = map fromPlatformString alias
+ ,rules_file = maybe Nothing (Just . fromPlatformString) rules_file
+ ,args_ = map fromPlatformString args_
+ }
+
+-- | Convert any relative dates within these options, except for the
+-- period option, to fixed dates, based on today's date. Note this means
+-- the dates specified by a period expression can change if the date
+-- changes during a program run, whereas begin, end, display option dates
+-- are fixed at startup.
+fixMostOptDates :: Opts -> IO Opts
+fixMostOptDates opts@Opts{..} = do
+ d <- getCurrentDay
+ let fixbracketeddatestr "" = ""
+ fixbracketeddatestr s = "[" ++ fixSmartDateStr d (init $ tail s) ++ "]"
+ return $ opts {
+ begin = maybe Nothing (Just . fixSmartDateStr d) begin
+ ,end = maybe Nothing (Just . fixSmartDateStr d) end
+ ,display = maybe Nothing (Just . regexReplaceBy "\\[.+?\\]" fixbracketeddatestr) display
+ }
+
+checkOpts :: Opts -> IO Opts
+checkOpts opts = do
+ case formatFromOpts opts of
+ Left err -> optsError err
+ Right _ -> return ()
+ d <- getCurrentDay
+ case maybe Nothing (Just . parsePeriodExpr d) $ period opts of
+ Just (Left perr) -> optsError $ show perr
+ _ -> return ()
+ when (null $ args_ opts) $ optsError "a command is required."
+ return opts
+
+optsError :: String -> IO ()
+-- optsError s = putStrLn s >> exitWith (ExitFailure 1)
+optsError = ioError . userError' . (++ " Run with --help to see usage.")
+
+-- | Figure out the overall date span we should report on, based on any
+-- begin/end/period options provided and a reference date. If there is a
+-- period option, the others are ignored.
+dateSpanFromOpts :: Day -> Opts -> DateSpan
+dateSpanFromOpts d Opts{period=Just p} =
+ case parsePeriodExpr d p of
+ Right (_, span) -> span
+ Left e -> error' $ "could not parse period option: "++show e
+dateSpanFromOpts d Opts{..} = DateSpan (maybeday begin) (maybeday end)
+ where
+ maybeday = maybe Nothing (Just . parsedate . fixSmartDateStr d)
+
+-- | Figure out the reporting interval, if any, specified by the options.
+-- --period overrides --daily overrides --weekly overrides --monthly etc.
+intervalFromOpts :: Opts -> Interval
+intervalFromOpts Opts{period=Just p} =
+ case parsePeriodExpr (parsedate "0001/01/01") p of
+ Right (interval, _) -> interval
+ Left e -> error' $ "could not parse period option: "++show e
+intervalFromOpts Opts{..} =
+ if daily then Days 1
+ else if weekly then Weeks 1
+ else if monthly then Months 1
+ else if quarterly then Quarters 1
+ else if yearly then Years 1
+ else NoInterval
+
+-- | Parse the format option if any, or raise an error if parsing fails.
+formatFromOpts :: Opts -> Either String [FormatString]
+formatFromOpts opts = maybe (Right defaultBalanceFormatString) parseFormatString $ format opts
+
+-- | Default line format for balance report: "%20(total) %2(depth_spacer)%-(account)"
+defaultBalanceFormatString :: [FormatString]
+defaultBalanceFormatString = [
+ FormatField False (Just 20) Nothing Total
+ , FormatLiteral " "
+ , FormatField True (Just 2) Nothing DepthSpacer
+ , FormatField True Nothing Nothing Format.Account
+ ]
+
+-- | Get the value of the baseurl option, if any.
+baseUrlFromOpts :: Opts -> Maybe String
+baseUrlFromOpts = const Nothing -- base_url
+
+-- | Get the value of the (last) port option, if any.
+portFromOpts :: Opts -> Maybe Int
+portFromOpts = const Nothing -- port
+
+-- | Get a maybe boolean representing the last cleared/uncleared option if any.
+clearedValueFromOpts :: Opts -> Maybe Bool
+clearedValueFromOpts Opts{..} | cleared_ = Just True
+ | uncleared = Just False
+ | otherwise = Nothing
+
+-- | Detect which date we will report on, based on --effective.
+whichDateFromOpts :: Opts -> WhichDate
+whichDateFromOpts opts = if effective opts then EffectiveDate else ActualDate
+
+-- | Get the journal file path from options, an environment variable, or a default
+journalFilePathFromOpts :: Opts -> IO String
+journalFilePathFromOpts opts = do
+ istimequery <- usingTimeProgramName
+ f <- if istimequery then myTimelogPath else myJournalPath
+ return $ fromMaybe f $ file opts
+
+-- | Were we invoked as \"hours\" ?
+usingTimeProgramName :: IO Bool
+usingTimeProgramName = do
+ progname <- getProgName
+ return $ map toLower progname == progname_cli_time
+
+aliasesFromOpts :: Opts -> [(AccountName,AccountName)]
+aliasesFromOpts = map parseAlias . alias
+ where
+ -- similar to ledgerAlias
+ parseAlias :: String -> (AccountName,AccountName)
+ parseAlias s = (accountNameWithoutPostingType $ strip orig
+ ,accountNameWithoutPostingType $ strip alias')
+ where
+ (orig, alias) = break (=='=') s
+ alias' = case alias of ('=':rest) -> rest
+ _ -> orig
+
+command :: Opts -> String
+command = headDef "" . args_
+
+patterns :: Opts -> [String]
+patterns = tailDef [] . args_
+
+-- | Gather filter pattern arguments into a list of account patterns and a
+-- list of description patterns. We interpret pattern arguments as
+-- follows: those prefixed with "desc:" are description patterns, all
+-- others are account patterns; also patterns prefixed with "not:" are
+-- negated. not: should come after desc: if both are used.
+parsePatternArgs :: [String] -> ([String],[String])
+parsePatternArgs args = (as, ds')
+ where
+ descprefix = "desc:"
+ (ds, as) = partition (descprefix `isPrefixOf`) args
+ ds' = map (drop (length descprefix)) ds
+
+-- | Convert application options to the library's generic filter specification.
+optsToFilterSpec :: Opts -> Day -> FilterSpec
+optsToFilterSpec opts d = FilterSpec {
+ datespan=dateSpanFromOpts d opts
+ ,cleared=clearedValueFromOpts opts
+ ,real=real_ opts
+ ,empty=empty_ opts
+ ,acctpats=apats
+ ,descpats=dpats
+ ,depth = depth_ opts
+ }
+ where (apats,dpats) = parsePatternArgs $ patterns opts
+
+tests_Hledger_Cli_Options = TestList
+ [
+ "dateSpanFromOpts" ~: do
+ let todaysdate = parsedate "2008/11/26"
+ gives = is . show . dateSpanFromOpts todaysdate
+ defopts `gives` "DateSpan Nothing Nothing"
+ defopts{begin=Just "2008",end=Just "2009"} `gives` "DateSpan (Just 2008-01-01) (Just 2009-01-01)"
+ defopts{period=Just "in 2008"} `gives` "DateSpan (Just 2008-01-01) (Just 2009-01-01)"
+ defopts{begin=Just "2005",end=Just "2007",period=Just "in 2008"} `gives` "DateSpan (Just 2008-01-01) (Just 2009-01-01)"
+
+ ,"intervalFromOpts" ~: do
+ let gives = is . intervalFromOpts
+ defopts `gives` NoInterval
+ defopts{daily=True} `gives` Days 1
+ defopts{weekly=True} `gives` Weeks 1
+ defopts{monthly=True} `gives` Months 1
+ defopts{quarterly=True} `gives` Quarters 1
+ defopts{yearly=True} `gives` Years 1
+ defopts{period=Just "weekly"} `gives` Weeks 1
+ defopts{period=Just "monthly"} `gives` Months 1
+ defopts{period=Just "quarterly"} `gives` Quarters 1
+ defopts{weekly=True,period=Just "yearly"} `gives` Years 1
+
+ ]
+***** cmdargs implicit ADT with extra options map
+data Opts = Opts {
+ -- :: -- hledger options
+ file :: Maybe FilePath
+ -- :: -- hledger-lib options
+ ,begin :: Maybe String
+ ,end :: Maybe String
+ ,period :: Maybe String
+ ,cleared_ :: Bool
+ ,uncleared :: Bool
+ ,cost :: Bool
+ ,depth_ :: Maybe Int
+ ,display :: Maybe String
+ ,effective :: Bool
+ ,empty_ :: Bool
+ ,no_elide :: Bool
+ ,real_ :: Bool
+ ,flat :: Bool
+ ,drop_ :: Int
+ ,no_total :: Bool
+ ,daily :: Bool
+ ,weekly :: Bool
+ ,monthly :: Bool
+ ,quarterly :: Bool
+ ,yearly :: Bool
+ ,format :: Maybe String
+ -- :: -- hledger options
+ ,alias :: [String]
+ ,no_new_accounts :: Bool
+ ,rules_file :: Maybe String
+ ,binary_filename :: Bool
+ ,debug :: Bool
+
+ -- :: -- add-ons' extra options
+ ,extra_opts :: M.Map String String
+-- #ifdef HLEDGERVTY
+-- ,debug_vty :: Bool
+-- #endif
+-- #ifdef HLEDGERWEB
+-- ,base_url :: Maybe String
+-- ,port :: Maybe Int
+-- #endif
+-- #ifdef HLEDGERCHART
+-- ,chart_output :: Maybe String
+-- ,chart_items :: Maybe Int
+-- ,chart_size :: Maybe String
+-- #endif
+
+ ,args_ :: [String]
+
+ } deriving (Show, Data, Typeable)
+
+***** cmdargs explicit string map
+progname = Hledger.Cli.progname ++ "-vty"
+progversion = progversionstr progname
+
+-- usage_preamble =
+-- "Usage: hledger-vty [OPTIONS] [PATTERNS]\n" ++
+-- "\n" ++
+-- "Reads your ~/.hledger.journal file, or another specified by $LEDGER_FILE or -f, and\n" ++
+-- "starts the full-window curses ui.\n" ++
+-- "\n"
+
+type Opts = Map String String
+
+vtymode :: Mode Opts
+vtymode = mode "hledger-vty options" M.empty "general and hledger-vty-specific options" vtyargs vtyflags
+vtyargs = flagArg (\v opts -> Right $ M.insert "dummy args flag" v opts) "DUMMY"
+vtyflags = [
+ flagReq ["begin","b"] (\v opts -> Right $ M.insert "begin" v opts) "DATE" "report on transactions on or after this date"
+ ,flagReq ["end","e"] (\v opts -> Right $ M.insert "end" v opts) "DATE" "report on transactions before this date"
+ ,flagReq ["period","p"] (\v opts -> Right $ M.insert "period" v opts) "PERIODEXPR" "report on transactions during the specified period and/or with the specified reporting interval"
+ ,flagHelpSimple id
+ ]
+
+-- defopts = Hledger.Cli.defopts {
+-- debug_vty = def &= help "run with no terminal output, showing console"
+-- ,args_ = def &= args &= typ "PATTERNS"
+-- }
+-- &= program progname
+-- &= summary progversion
+
+getHledgerVtyOpts :: IO Opts
+getHledgerVtyOpts = processArgs vtymode -- >>= processOpts >>= checkOpts
+
+-- processOpts :: Opts -> IO Opts
+-- processOpts = Hledger.Cli.processOpts
+
+-- checkOpts :: Opts -> IO Opts
+-- checkOpts = Hledger.Cli.checkOpts
+
+main :: IO ()
+main = do
+ opts <- getHledgerVtyOpts
+ when ("debug" `M.member` opts) $ printf "%s\n" progversion >> printf "opts: %s\n" (show opts)
+ return () -- runWith opts
+
+runWith :: Opts -> IO ()
+runWith opts
+ | "binary_filename" `M.member` opts = putStrLn (binaryfilename progname_cli)
+ | otherwise = withJournalDo opts vty
+
+***** cmdargs very explicit string map
+import Control.Monad
+import Data.Map as M
+import Data.Time.Calendar
+import System.Console.CmdArgs
+import System.Console.CmdArgs.Explicit
+import System.Console.CmdArgs.Text
+
+in_ = M.member
+
+type Opts = Map String String
+
+vtyargs = flagArg (\v opts -> Right $ M.insert "PATTERNS" v opts) "query patterns"
+
+vtyflags = [
+ flagNone ["debug-vty"] (\opts -> M.insert "debug-vty" "" opts) "run with no terminal output, showing console"
+ ]
+
+commonflags = [
+ flagReq ["begin","b"] (\v opts -> Right $ M.insert "begin" v opts) "DATE" "report on transactions on or after this date"
+ ,flagReq ["end","e"] (\v opts -> Right $ M.insert "end" v opts) "DATE" "report on transactions before this date"
+ ,flagReq ["period","p"] (\v opts -> Right $ M.insert "period" v opts) "PERIODEXPR" "report on transactions during the specified period and/or with the specified reporting interval"
+ ,flagHelpSimple (M.insert "help" "")
+ ,flagVersion (M.insert "version" "")
+ ]
+
+vtymode :: Mode Opts
+vtymode = Mode {
+ modeGroupModes = toGroup []
+ ,modeNames = ["vty"]
+ ,modeValue = M.empty
+ ,modeCheck = Right
+ ,modeReform = const Nothing
+ ,modeHelp = ""
+ ,modeHelpSuffix = []
+ ,modeArgs = Nothing
+ ,modeGroupFlags = Group {
+ groupUnnamed = []
+ ,groupHidden = []
+ ,groupNamed = [
+ ("vty options", vtyflags)
+ ,("general options", commonflags)
+ ]
+ }
+ }
+
+main = do
+ opts <- processArgs vtymode
+ print opts
+ when ("help" `in_` opts) $ putStr $ showText defaultWrap $ helpText HelpFormatDefault vtymode
+
+optsToFilterSpec :: Opts -> Day -> FilterSpec
+optsToFilterSpec opts d = FilterSpec {
+ datespan=nulldatespan -- dateSpanFromOpts d opts
+ ,cleared=clearedValueFromOpts opts
+ ,real="real" `in_` opts
+ ,empty="empty" `in_` opts
+ ,acctpats=[] --apats
+ ,descpats=[] -- dpats
+ ,depth = maybe Nothing (Just . read) $ M.lookup "depth" opts
+ }
+ where (apats,dpats) = parsePatternArgs $ patterns opts
+
+clearedValueFromOpts opts | "cleared" `in_` opts = Just True
+ | "uncleared" `in_` opts = Just False
+ | otherwise = Nothing
+
+-- dateSpanFromOpts :: Day -> Opts -> DateSpan
+-- dateSpanFromOpts d Opts{period=Just p} =
+-- case parsePeriodExpr d p of
+-- Right (_, span) -> span
+-- Left e -> error' $ "could not parse period option: "++show e
+-- dateSpanFromOpts d Opts{..} = DateSpan (maybeday begin) (maybeday end)
+-- where
+-- maybeday = maybe Nothing (Just . parsedate . fixSmartDateStr d)
+
+***** cmdargs explicit string map -> separate ADTs
+-- 1. option values for use in this and maybe other packages. These are the data we want to collect.
+
+-- report options, used in hledger-lib and above
+data ReportOpts = ReportOpts {
+ begin_ :: Maybe Day
+ ,end_ :: Maybe Day
+ ,period_ :: Maybe (DateSpan,Interval)
+ ,cleared_ :: Bool
+ ,uncleared_ :: Bool
+ ,cost_ :: Bool
+ ,depth_ :: Maybe Int
+ ,display_ :: Maybe String
+ ,effective_ :: Bool
+ ,empty_ :: Bool
+ ,no_elide_ :: Bool
+ ,real_ :: Bool
+ ,flat_ :: Bool
+ ,drop_ :: Int
+ ,no_total_ :: Bool
+ ,daily_ :: Bool
+ ,weekly_ :: Bool
+ ,monthly_ :: Bool
+ ,quarterly_ :: Bool
+ ,yearly_ :: Bool
+ ,format_ :: Maybe String
+ } deriving (Show) --, Data, Typeable)
+
+defreportopts = ReportOpts
+ def
+ def
+ def
+ def
+ def
+ def
+ def
+ def
+ def
+ def
+ def
+ def
+ def
+ def
+ def
+ def
+ def
+ def
+ def
+ def
+ def
+
+instance Default ReportOpts where def = defreportopts
+
+-- cli options, used in hledger and above
+data CliOpts = CliOpts {
+ file_ :: Maybe FilePath
+ ,alias_ :: [String]
+ ,binary_filename :: Bool
+ ,debug_ :: Bool
+ ,command_ :: String
+ ,patterns_ :: [String]
+ -- add
+ ,no_new_accounts_ :: Bool
+ -- convert
+ ,rules_file_ :: Maybe FilePath
+
+ ,reportopts_ :: ReportOpts
+ } deriving (Show) --, Data, Typeable)
+
+defcliopts = CliOpts
+ def
+ def
+ def
+ def
+ def
+ def
+ def
+ def
+ def
+
+instance Default CliOpts where def = defcliopts
+
+-- 2. reusable/extensible command-line flags, help and initial parsing for the above.
+
+generalflags = [
+ flagReq ["file","f"] (\v opts -> Right $ M.insert "file" "" opts) "FILE" "use a different journal file; - means stdin"
+ ,flagHelpSimple (M.insert "help" "")
+ ,flagVersion (M.insert "version" "")
+ ,flagReq ["begin","b"] (\v opts -> Right $ M.insert "begin" v opts) "DATE" "report on transactions on or after this date"
+ ,flagReq ["end","e"] (\v opts -> Right $ M.insert "end" v opts) "DATE" "report on transactions before this date"
+ ,flagReq ["period","p"] (\v opts -> Right $ M.insert "period" v opts) "PERIODEXPR" "report on transactions during the specified period and/or with the specified reporting interval"
+ ]
+
+addflags = [
+ flagNone ["no-new-accounts"] (\opts -> M.insert "no-new-accounts" "" opts) ""
+ ]
+
+convertflags = [
+ flagReq ["rules-file"] (\v opts -> Right $ M.insert "rules-file" v opts) "FILE" ""
+ ]
+
+cliargs = flagArg (\v opts -> Right $ M.insert "args" v opts) "COMMAND [PATTERNS]"
+
+type RawOpts = Map String String
+
+progmode :: Mode RawOpts
+progmode = Mode {
+ modeGroupModes = toGroup []
+ ,modeNames = ["hledger"]
+ ,modeValue = M.empty
+ ,modeCheck = Right
+ ,modeReform = const Nothing
+ ,modeHelp = "hledger options test"
+ ,modeHelpSuffix = []
+ ,modeArgs = Just cliargs
+ ,modeGroupFlags = Group {
+ groupUnnamed = []
+ ,groupHidden = []
+ ,groupNamed = [("general options", generalflags)
+ ,("add options", addflags)
+ ,("convert options", convertflags)
+ ]
+ }
+ }
+
+-- 3. post-processing, additional checking and conversion of raw options
+-- data Opts = Opts { cliopts :: CliOpts, reportopts :: ReportOpts } deriving (Show)
+-- defopts = Opts defcliopts defreportopts
+
+processOpts :: RawOpts -> CliOpts
+processOpts rawopts = defcliopts {
+ file_ = stringopt "file" rawopts
+ ,debug_ = boolopt "debug" rawopts
+ ,reportopts_ = defreportopts {
+ begin_ = maybedateopt "begin" rawopts
+ ,end_ = maybedateopt "end" rawopts
+ }
+ }
+ where
+ optserror = error'
+ boolopt = M.member
+ stringopt = M.lookup
+ maybedateopt name rawopts =
+ case M.lookup name rawopts of
+ Nothing -> Nothing
+ Just s -> Just $ fromMaybe (optserror $ "could not parse "++name++" date: "++s) $ parsedateM s
+
+
+
+testmain = do
+ rawopts <- processArgs progmode
+ print rawopts
+ print $ processOpts rawopts
+ when ("help" `in_` rawopts) $ putStr $ showText defaultWrap $ helpText HelpFormatDefault progmode
+
+***** other things to try:
+http://www.haskell.org/haskellwiki/Heterogenous_collections
+make cmdargs generate flat help from nested ADTs
+make cmdargs pass through extra opts without --
+**** todo
+move *FromOpts into toOpts
+
+*** use matchers for command line
+**** todo
+design cli, backwards compatibility ?
+replace optsToFilterSpec
+
+*** review/simplify apis
+simplify option types ?
+
+*** include latest jquery, jquery-url, minified and non
+http://ajaxcssblog.com/jquery/url-read-request-variables/
*** more modularity
**** packages/namespace
***** more package splits ?
@@ -572,7 +1777,63 @@ http://msdn.microsoft.com/en-us/library/ms714415(v=VS.85).aspx
**** unix/windows/mac platform
*** inspiration
http://community.haskell.org/~ndm/downloads/paper-hoogle_overview-19_nov_2008.pdf -> Design Guidelines
-** features
+** features/wishlist
+*** setting balances
+You can accomplish "setting to the bank's view" with a transaction like this:
+
+2011-08-12 Sample
+ Assets:Checking = $200.00
+ Equity:Adjustments
+
+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.
+
+what about balance assertions ?
+
+*** --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.
+*** directives without !
+*** account prefix should remain in effect till end directive or end of all data, like ledger
+*** account prefix should preserve non-balanced postings
+*** account prefix should be a special case of alias ?
+*** alias directives should be modified by account directives
+*** -X, report in target commodity
+cf http://bugs.ledger-cli.org/show_bug.cgi?id=538
+*** account aliasing/aligning
+**** Martin Wuertele:
+I see our task not in keeping accounts (that's in the responsibility of
+the trusted bodies) but more in management accounts. In order to achieve
+that we need a solution that mirrors the financials of the trusted
+bodies, has a way to streamline them (allign different local chart of
+accounts or reporting formats to an unified one), do some
+reclassifications and accruals on top, performe currency conversions (we
+have debian.uk, debian.ch, FFIS, SPI-INC,...), accumulate the results,
+add additional reclassifications and accruals on top and, in some cases,
+add consolidation entries (e.g. SPI-INC does reembursement but gets
+itself reembursed by FFIS).
+
+We do not bother with any local tasks like income tax, vat or
+statistical filing, invoicing and the like.
+
+*** can't find out total income:7th total in june at http://localhost:5005/register?q=inacct%3Aassets%3Atreasury !
+*** journalAddTransaction should check txn balances
+*** parsing: ignore tag, pop
+
+Feb 26 2009
+Just added a new feature to better improve 3.0's use of metadata:
+There are now "tag/pop" directives, to apply metadata to a range of
+transactions (and their postings). For example, if you wanted a
+conceptual "page" of transactions relating to business trip to
+Chicago, you could do this:
+ tag Location: Chicago
+ tag Purpose: Business
+ ... transactions go here
+ pop
+ pop
+It would be as if you'd applied "; Location: Chicago", etc., to every
+transaction.
+
*** cli, web: richer filter patterns/query language
ledger's query syntax: http://ledger-cli.org/3.0/doc/ledger.1.html
**** draft 1:
@@ -816,7 +2077,6 @@ types need converting, etc.
plugins may run more slowly
plugins can be discovered/loaded by module path or by loading files directly
-
* misc
** things I want to know
*** time
@@ -863,8 +2123,6 @@ are there any cashflow, tax, budgetary problems looming ?
*** http://www.n-heptane.com/nhlab/repos/Decimal/Money.hs
*** http://www2.hursley.ibm.com/decimal/
** docs
-*** http://current.workingdirectory.net/posts/2011/gnucash-python-bindings
-*** http://hackage.haskell.org/trac/ghc/wiki/Commentary/CodingStyle
*** 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
*** lwn grumpy editor articles
@@ -1017,11 +2275,19 @@ and there's also a budget report feature
a catalog of standard bookkeeping entries for typical real-world transactions is really helpful and worth searching for
+*** 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
** software
*** http://gnucash.org
*** http://www.xtuple.com/postbooks
*** http://weberp.org
**** http://www.weberp.org/weberp/doc/Manual/ManualContents.php
+*** http://www.clientsandprofits.com
** selinger article on currency & capital gains accounting
http://www.mscs.dal.ca/~selinger/accounting/tutorial.html#1.2
** hledger feedback
@@ -1064,27 +2330,6 @@ http://www.mscs.dal.ca/~selinger/accounting/tutorial.html#1.2
** code snippets
- hopefully debugging coffeescript won't be too troublesome :) [20:41]
- luite: only a little- it makes you more productive overall (at least
- once you are doing a decent amount of javascript in your app). I also
- recommend underscore.js for functional helpers. [20:52]
-
-compiling coffeescript in yesod (gweber)
-
- defaultLayout widget = doe
- y <- getYesod
- mroute <- liftIO $ readIORef $ staticRenders y
- coffeeUrl <- case mroute of
- Just r -> return r
- Nothing -> do
- renderUrl <- getUrlRenderParams
- c <- liftIO $ renderCoffee renderUrl $(Settings.coffeeFile "search")
- Just (Right (u, p)) <- addStaticContent "js" "text/javascript; charset=utf-8"
- $ encodeUtf8 c
- let r = renderUrl u p
- liftIO $ writeIORef (staticRenders y) (Just r)
- return r
-
fromOfxTransaction :: StatementTransaction -> LedgerTransaction
fromOfxTransaction StatementTransaction {
stType = _ --sttype -- :: TransactionType