packages feed

hledger 0.19.3 → 0.20

raw patch · 8 files changed

+302/−282 lines, 8 filesdep ~hledger-libPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: hledger-lib

API changes (from Hackage documentation)

- Hledger.Cli.Add: PostingState :: Journal -> (AccountName -> Bool) -> Bool -> Maybe [Posting] -> PostingState
- Hledger.Cli.Add: data PostingState
- Hledger.Cli.Add: getAndAddTransactions :: Journal -> CliOpts -> Day -> IO ()
- Hledger.Cli.Add: getPostings :: PostingState -> [Posting] -> IO [Posting]
- Hledger.Cli.Register: showPostingWithBalanceForVty :: Bool -> Posting -> MixedAmount -> String
+ Hledger.Cli.Add: PostingsState :: Journal -> (AccountName -> Bool) -> Bool -> Maybe [Posting] -> PostingsState
+ Hledger.Cli.Add: RestartEntryException :: RestartEntryException
+ Hledger.Cli.Add: capitalize :: String -> String
+ Hledger.Cli.Add: data PostingsState
+ Hledger.Cli.Add: data RestartEntryException
+ Hledger.Cli.Add: getAndAddTransactionsLoop :: Journal -> CliOpts -> String -> [String] -> IO ()
+ Hledger.Cli.Add: getPostingsForTransactionWithHistory :: Journal -> CliOpts -> String -> String -> String -> String -> [String] -> IO (Maybe Transaction)
+ Hledger.Cli.Add: getPostingsLoop :: PostingsState -> [Posting] -> [String] -> IO [Posting]
+ Hledger.Cli.Add: getTransactionOrRestart :: Journal -> CliOpts -> String -> [String] -> IO (Maybe Transaction)
+ Hledger.Cli.Add: headTailDef :: a -> [a] -> (a, [a])
+ Hledger.Cli.Add: instance Exception RestartEntryException
+ Hledger.Cli.Add: instance Show RestartEntryException
+ Hledger.Cli.Add: instance Typeable RestartEntryException
+ Hledger.Cli.Options: Auto :: Width
+ Hledger.Cli.Options: FieldWidths :: [Width] -> OutputWidth
+ Hledger.Cli.Options: TotalWidth :: Width -> OutputWidth
+ Hledger.Cli.Options: Width :: Int -> Width
+ Hledger.Cli.Options: data OutputWidth
+ Hledger.Cli.Options: data Width
+ Hledger.Cli.Options: defaultWidth :: Int
+ Hledger.Cli.Options: defaultWidthWithFlag :: Int
+ Hledger.Cli.Options: instance Show OutputWidth
+ Hledger.Cli.Options: instance Show Width
+ Hledger.Cli.Options: outputwidth :: GenParser Char st OutputWidth
+ Hledger.Cli.Options: parseWidth :: String -> Either String OutputWidth
+ Hledger.Cli.Options: width :: GenParser Char st Width
+ Hledger.Cli.Options: widthFromOpts :: CliOpts -> Either String OutputWidth
+ Hledger.Cli.Options: width_ :: CliOpts -> Maybe String
- Hledger.Cli.Add: getTransaction :: Journal -> CliOpts -> Day -> IO (Transaction, Day)
+ Hledger.Cli.Add: getTransaction :: Journal -> CliOpts -> String -> [String] -> IO Transaction
- Hledger.Cli.Add: psAccept :: PostingState -> AccountName -> Bool
+ Hledger.Cli.Add: psAccept :: PostingsState -> AccountName -> Bool
- Hledger.Cli.Add: psHistory :: PostingState -> Maybe [Posting]
+ Hledger.Cli.Add: psHistory :: PostingsState -> Maybe [Posting]
- Hledger.Cli.Add: psJournal :: PostingState -> Journal
+ Hledger.Cli.Add: psJournal :: PostingsState -> Journal
- Hledger.Cli.Add: psSuggestHistoricalAmount :: PostingState -> Bool
+ Hledger.Cli.Add: psSuggestHistoricalAmount :: PostingsState -> Bool
- Hledger.Cli.Options: CliOpts :: RawOpts -> String -> Maybe FilePath -> Maybe FilePath -> [String] -> Bool -> Bool -> ReportOpts -> CliOpts
+ Hledger.Cli.Options: CliOpts :: RawOpts -> String -> Maybe FilePath -> Maybe FilePath -> [String] -> Bool -> Bool -> Maybe String -> ReportOpts -> CliOpts
- Hledger.Cli.Register: postingsReportAsText :: ReportOpts -> PostingsReport -> String
+ Hledger.Cli.Register: postingsReportAsText :: CliOpts -> PostingsReport -> String

Files

Hledger/Cli.hs view
@@ -336,214 +336,94 @@           [            txnTieKnot $ Transaction {              tdate=parsedate "2007/01/01",-             teffectivedate=Nothing,+             tdate2=Nothing,              tstatus=False,              tcode="*",              tdescription="opening balance",              tcomment="",              ttags=[],-             tpostings=[-              Posting {-                pstatus=False,-                paccount="assets:cash",-                pamount=(Mixed [usd 4.82]),-                pcomment="",-                ptype=RegularPosting,-                ptags=[],-                ptransaction=Nothing-              },-              Posting {-                pstatus=False,-                paccount="equity:opening balances",-                pamount=(Mixed [usd (-4.82)]),-                pcomment="",-                ptype=RegularPosting,-                ptags=[],-                ptransaction=Nothing-              }-             ],+             tpostings=+                 ["assets:cash" `post` usd 4.82+                 ,"equity:opening balances" `post` usd (-4.82)+                 ],              tpreceding_comment_lines=""            }           ,            txnTieKnot $ Transaction {              tdate=parsedate "2007/02/01",-             teffectivedate=Nothing,+             tdate2=Nothing,              tstatus=False,              tcode="*",              tdescription="ayres suites",              tcomment="",              ttags=[],-             tpostings=[-              Posting {-                pstatus=False,-                paccount="expenses:vacation",-                pamount=(Mixed [usd 179.92]),-                pcomment="",-                ptype=RegularPosting,-                ptags=[],-                ptransaction=Nothing-              },-              Posting {-                pstatus=False,-                paccount="assets:checking",-                pamount=(Mixed [usd (-179.92)]),-                pcomment="",-                ptype=RegularPosting,-                ptags=[],-                ptransaction=Nothing-              }-             ],+             tpostings=+                 ["expenses:vacation" `post` usd 179.92+                 ,"assets:checking" `post` usd (-179.92)+                 ],              tpreceding_comment_lines=""            }           ,            txnTieKnot $ Transaction {              tdate=parsedate "2007/01/02",-             teffectivedate=Nothing,+             tdate2=Nothing,              tstatus=False,              tcode="*",              tdescription="auto transfer to savings",              tcomment="",              ttags=[],-             tpostings=[-              Posting {-                pstatus=False,-                paccount="assets:saving",-                pamount=(Mixed [usd 200]),-                pcomment="",-                ptype=RegularPosting,-                ptags=[],-                ptransaction=Nothing-              },-              Posting {-                pstatus=False,-                paccount="assets:checking",-                pamount=(Mixed [usd (-200)]),-                pcomment="",-                ptype=RegularPosting,-                ptags=[],-                ptransaction=Nothing-              }-             ],+             tpostings=+                 ["assets:saving" `post` usd 200+                 ,"assets:checking" `post` usd (-200)+                 ],              tpreceding_comment_lines=""            }           ,            txnTieKnot $ Transaction {              tdate=parsedate "2007/01/03",-             teffectivedate=Nothing,+             tdate2=Nothing,              tstatus=False,              tcode="*",              tdescription="poquito mas",              tcomment="",              ttags=[],-             tpostings=[-              Posting {-                pstatus=False,-                paccount="expenses:food:dining",-                pamount=(Mixed [usd 4.82]),-                pcomment="",-                ptype=RegularPosting,-                ptags=[],-                ptransaction=Nothing-              },-              Posting {-                pstatus=False,-                paccount="assets:cash",-                pamount=(Mixed [usd (-4.82)]),-                pcomment="",-                ptype=RegularPosting,-                ptags=[],-                ptransaction=Nothing-              }-             ],+             tpostings=+                 ["expenses:food:dining" `post` usd 4.82+                 ,"assets:cash" `post` usd (-4.82)+                 ],              tpreceding_comment_lines=""            }           ,            txnTieKnot $ Transaction {              tdate=parsedate "2007/01/03",-             teffectivedate=Nothing,+             tdate2=Nothing,              tstatus=False,              tcode="*",              tdescription="verizon",              tcomment="",              ttags=[],-             tpostings=[-              Posting {-                pstatus=False,-                paccount="expenses:phone",-                pamount=(Mixed [usd 95.11]),-                pcomment="",-                ptype=RegularPosting,-                ptags=[],-                ptransaction=Nothing-              },-              Posting {-                pstatus=False,-                paccount="assets:checking",-                pamount=(Mixed [usd (-95.11)]),-                pcomment="",-                ptype=RegularPosting,-                ptags=[],-                ptransaction=Nothing-              }-             ],+             tpostings=+                 ["expenses:phone" `post` usd 95.11+                 ,"assets:checking" `post` usd (-95.11)+                 ],              tpreceding_comment_lines=""            }           ,            txnTieKnot $ Transaction {              tdate=parsedate "2007/01/03",-             teffectivedate=Nothing,+             tdate2=Nothing,              tstatus=False,              tcode="*",              tdescription="discover",              tcomment="",              ttags=[],-             tpostings=[-              Posting {-                pstatus=False,-                paccount="liabilities:credit cards:discover",-                pamount=(Mixed [usd 80]),-                pcomment="",-                ptype=RegularPosting,-                ptags=[],-                ptransaction=Nothing-              },-              Posting {-                pstatus=False,-                paccount="assets:checking",-                pamount=(Mixed [usd (-80)]),-                pcomment="",-                ptype=RegularPosting,-                ptags=[],-                ptransaction=Nothing-              }-             ],+             tpostings=+                 ["liabilities:credit cards:discover" `post` usd 80+                 ,"assets:checking" `post` usd (-80)+                 ],              tpreceding_comment_lines=""            }           ]          }  ledger7 = ledgerFromJournal Any journal7---- journal8_str = unlines---  ["2008/1/1 test           "---  ,"  a:b          10h @ $40"---  ,"  c:d                   "---  ,""---  ]---- timelogentry1_str  = "i 2007/03/11 16:19:00 hledger\n"--- timelogentry1 = TimeLogEntry In (parsedatetime "2007/03/11 16:19:00") "hledger"---- timelogentry2_str  = "o 2007/03/11 16:30:00\n"--- timelogentry2 = TimeLogEntry Out (parsedatetime "2007/03/11 16:30:00") ""---- a1 = Mixed [(hrs 1){aprice=Just $ Mixed [Amount (comm "$") 10 Nothing]}]--- a2 = Mixed [(hrs 2){aprice=Just $ Mixed [Amount (comm "EUR") 10 Nothing]}]--- a3 = Mixed $ amounts a1 ++ amounts a2---- journalWithAmounts :: [String] -> Journal--- journalWithAmounts as =---        nulljournal{jtxns=---         [t | a <- as, let t = nulltransaction{tdescription=a,tpostings=[nullposting{pamount=parse a,ptransaction=Just t}]}]---         }---     where parse = fromparse . parseWithCtx nullctx amountp
Hledger/Cli/Add.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE ScopedTypeVariables, DeriveDataTypeable #-} {-|   A history-aware add command to help with data entry.@@ -10,16 +11,17 @@  module Hledger.Cli.Add where-import Control.Exception as C+import Control.Exception as E import Control.Monad import Control.Monad.Trans (liftIO)-import Data.Char (toUpper)+import Data.Char (toUpper, toLower) import Data.List import Data.Maybe-import Data.Time.Calendar+import Data.Typeable (Typeable)+import Safe (headDef, tailDef, headMay) import System.Console.Haskeline (InputT, runInputT, defaultSettings, setComplete, getInputLine) import System.Console.Haskeline.Completion-import System.IO ( stderr, hPutStrLn, hPutStr )+import System.IO ( stderr, hPutStr, hPutStrLn ) import System.IO.Error import Text.ParserCombinators.Parsec import Text.Printf@@ -32,55 +34,107 @@ import Hledger.Cli.Register (postingsReportAsText)  -{- | Information used as the basis for suggested account names, amounts,-     etc in add prompt--}-data PostingState = PostingState {-      psJournal :: Journal,-      psAccept  :: AccountName -> Bool,-      psSuggestHistoricalAmount :: Bool,-      psHistory :: Maybe [Posting]}---- | Read transactions from the terminal, prompting for each field,--- and append them to the journal file. If the journal came from stdin, this--- command has no effect.+-- | Read multiple transactions from the console, prompting for each+-- field, and append them to the journal file.  If the journal came+-- from stdin, this command has no effect. add :: CliOpts -> Journal -> IO () add opts j     | f == "-" = return ()     | otherwise = do-  hPrintf stderr "Adding transactions to journal file \"%s\".\n" f-  hPutStrLn stderr $-    "To complete a transaction, enter . (period) at an account prompt.\n"-    ++"To stop adding transactions, enter . at a date prompt, or control-d/control-c."-  today <- getCurrentDay-  getAndAddTransactions j opts today-        `C.catch` (\e -> unless (isEOFError e) $ ioError e)+  hPutStr stderr $ unlines [+     "Adding transactions to journal file "++f+    ,"Provide field values at the prompts, or press enter to accept defaults."+    ,"Use readline keys to edit, use tab key to complete account names."+    ,"A code (in parentheses) may be entered following transaction dates."+    ,"A comment may be entered following descriptions or amounts."+    ,"If you make a mistake, enter < at any prompt to restart the transaction."+    ,"To complete a transaction, enter . when prompted."+    ,"To quit, press control-d or control-c."+    ]+  today <- showDate `fmap` getCurrentDay+  let args                = words' $ query_ $ reportopts_ opts+      (defdate, moredefs) = headTailDef today args+  getAndAddTransactionsLoop j opts defdate moredefs+        `E.catch` (\e -> unless (isEOFError e) $ ioError e)       where f = journalFilePath j --- | Read a number of transactions from the command line, prompting,--- validating, displaying and appending them to the journal file, until--- end of input (then raise an EOF exception). Any command-line arguments--- are used as the first transaction's description.-getAndAddTransactions :: Journal -> CliOpts -> Day -> IO ()-getAndAddTransactions j opts defaultDate = do-  (t, d) <- getTransaction j opts defaultDate-  j <- journalAddTransaction j opts t-  getAndAddTransactions j opts d+-- | Loop reading transactions from the console, prompting for,+-- validating, displaying and appending each one to the journal file,+-- until end of input or ctrl-c (then raise an EOF exception).+-- If provided, command-line arguments are used as defaults for the+-- first transaction; otherwise defaults come from the most similar+-- recent transaction in the journal.+getAndAddTransactionsLoop :: Journal -> CliOpts -> String -> [String] -> IO ()+getAndAddTransactionsLoop j opts defdate moredefs = do+  hPrintf stderr "\nStarting a new transaction.\n"+  t <- getTransaction j opts defdate moredefs+  j' <- journalAddTransaction j opts t+  hPrintf stderr "Added to the journal.\n"+  let defdate' = showDate $ tdate t+  getAndAddTransactionsLoop j' opts defdate' [] --- | Read a transaction from the command line, with history-aware prompting.-getTransaction :: Journal -> CliOpts -> Day-                    -> IO (Transaction,Day)-getTransaction j opts defaultDate = do-  today <- getCurrentDay-  datestr <- runInteractionDefault $ askFor "date, or . to end"-            (Just $ showDate defaultDate)+-- | Read a single transaction from the console, with history-aware prompting,+-- allowing the user to restart and confirm at the end.+-- A default date, and zero or more defaults for subsequent fields, are provided.+getTransaction :: Journal -> CliOpts -> String -> [String] -> IO Transaction+getTransaction j opts defdate moredefs = do+  mt <- getTransactionOrRestart j opts defdate moredefs+  let restart = do+        hPrintf stderr "\nRestarting this transaction.\n"+        getTransaction j opts defdate moredefs+  case mt of+    Nothing -> restart+    Just t  -> do+      hPrintf stderr "\nTransaction entered:\n%s" (show t)+      yn <- runInteractionDefault $ askFor "Accept this transaction" (Just "y") (Just $ \s -> map toLower s `elem` ["<","y","yes","n","no"])+      case headMay $ map toLower yn of+        Just 'y' -> return t+        _        -> restart++-- | Read a single transaction from the console, with history-aware prompting,+-- or return nothing indicating that the user wants to restart entering this transaction.+-- A default date, and zero or more defaults for subsequent fields, are provided.+getTransactionOrRestart :: Journal -> CliOpts -> String -> [String] -> IO (Maybe Transaction)+getTransactionOrRestart j opts defdate moredefs = do+  let dateandcodep = do {d <- smartdate; c <- optionMaybe code; many spacenonewline; eof; return (d, fromMaybe "" c)}+  datecodestr <- runInteractionDefault $ askFor "date"+            (Just defdate)             (Just $ \s -> null s                          || s == "."-                         || isRight (parse (smartdate >> many spacenonewline >> eof) "" $ lowercase s))-  when (datestr == ".") $ ioError $ mkIOError eofErrorType "" Nothing Nothing-  description <- runInteractionDefault $ askFor "description" (Just "") Nothing+                         || isRight (parseWithCtx nullctx dateandcodep $ lowercase s))+  when (datecodestr == ".") $ ioError $ mkIOError eofErrorType "" Nothing Nothing+  today <- getCurrentDay+  let (sdate,code) = fromparse $ parseWithCtx nullctx dateandcodep datecodestr+      defday = fixSmartDate today $ fromparse $ (parse smartdate "" . lowercase) defdate+      datestr = showDate $ fixSmartDate defday sdate++  let (defdesc, moredefs') = headTailDef "" moredefs+  desc <- runInteractionDefault $ askFor "description" (Just defdesc) Nothing+  if desc == "<"+   then return Nothing+   else do+    let (description,comment) = (strip a, strip $ dropWhile (==';') b) where (a,b) = break (==';') desc+    getPostingsForTransactionWithHistory j opts datestr code description comment moredefs'++data RestartEntryException = RestartEntryException deriving (Typeable,Show)+instance Exception RestartEntryException++-- | Information used as the basis for suggested account names, amounts etc. in add prompt.+data PostingsState = PostingsState {+   psJournal                 :: Journal+  ,psAccept                  :: AccountName -> Bool+  ,psSuggestHistoricalAmount :: Bool+  ,psHistory                 :: Maybe [Posting]+  }++-- | Loop reading postings from the console, until a valid balanced+-- set of postings has been entered, then return the final transaction,+-- or nothing indicating that the user wants to restart entering this transaction.+getPostingsForTransactionWithHistory :: Journal -> CliOpts -> String -> String -> String -> String -> [String] -> IO (Maybe Transaction)+getPostingsForTransactionWithHistory j opts datestr code description comment defargs = do+  today <- getCurrentDay   let historymatches = transactionsSimilarTo j (queryFromOpts today $ reportopts_ opts) description-      bestmatch | null historymatches = Nothing+      bestmatch | not (null defargs) || null historymatches = Nothing                 | otherwise = Just $ snd $ head historymatches       bestmatchpostings = maybe Nothing (Just . tpostings) bestmatch       date = fixSmartDate today $ fromparse $ (parse smartdate "" . lowercase) datestr@@ -89,53 +143,56 @@             then x `elem` existingaccts             else True       existingaccts = journalAccountNames j-      getpostingsandvalidate = do-        ps <- getPostings (PostingState j accept True bestmatchpostings) []+      getvalidpostings = do+        ps <- getPostingsLoop (PostingsState j accept True bestmatchpostings) [] defargs         let t = nulltransaction{tdate=date                                ,tstatus=False+                               ,tcode=code                                ,tdescription=description+                               ,tcomment=comment                                ,tpostings=ps                                }-            retry msg = do-              liftIO $ hPutStrLn stderr $ "\n" ++ msg ++ "please re-enter."-              getpostingsandvalidate-        either retry (return . flip (,) date) $ balanceTransaction Nothing t -- imprecise balancing-  unless (null historymatches) -       (liftIO $ do-         hPutStrLn stderr "Similar transactions found, using the first for defaults:\n"-         hPutStr stderr $ concatMap (\(n,t) -> printf "[%3d%%] %s" (round $ n*100 :: Int) (show t)) $ take 3 historymatches)-  getpostingsandvalidate+        either retry (return . Just) $ balanceTransaction Nothing t -- imprecise balancing+          where+            retry msg = liftIO (hPutStrLn stderr $ "\n" ++ (capitalize msg) ++ "please re-enter.") >> getvalidpostings --- fragile--- | Read postings from the command line until . is entered, using any--- provided historical postings and the journal context to guess defaults.-getPostings :: PostingState -> [Posting] -> IO [Posting]-getPostings st enteredps = do+  when (isJust bestmatch) $ liftIO $ hPrintf stderr "\nUsing this existing transaction for defaults:\n%s" (show $ fromJust bestmatch)+  getvalidpostings `E.catch` \(_::RestartEntryException) -> return Nothing++-- | Read postings from the command line until . is entered, generating+-- useful defaults based on historical context and postings entered so far.+getPostingsLoop :: PostingsState -> [Posting] -> [String] -> IO [Posting]+getPostingsLoop st enteredps defargs = do   let bestmatch | isNothing historicalps = Nothing                 | n <= length ps = Just $ ps !! (n-1)                 | otherwise = Nothing                 where Just ps = historicalps-      defaultaccount = maybe Nothing (Just . showacctname) bestmatch+      bestmatchacct = maybe Nothing (Just . showacctname) bestmatch+      defacct  = maybe bestmatchacct Just $ headMay defargs+      defargs' = tailDef [] defargs       ordot | null enteredps || length enteredrealps == 1 = "" :: String-            | otherwise = ", or . to record"-  account <- runInteraction j $ askFor (printf "account %d%s" n ordot) defaultaccount (Just accept)+            | otherwise = " (or . to complete this transaction)"+  account <- runInteraction j $ askFor (printf "account %d%s" n ordot) defacct (Just accept)+  when (account=="<") $ throwIO RestartEntryException   if account=="."     then      if null enteredps       then do hPutStrLn stderr $ "\nPlease enter some postings first."-              getPostings st enteredps+              getPostingsLoop st enteredps defargs       else return enteredps     else do-      let defaultacctused = Just account == defaultaccount-          historicalps' = if defaultacctused then historicalps else Nothing+      let defacctused = Just account == defacct+          historicalps' = if defacctused then historicalps else Nothing           bestmatch' | isNothing historicalps' = Nothing                      | n <= length ps = Just $ ps !! (n-1)                      | otherwise = Nothing                      where Just ps = historicalps'-          defaultamountstr | isJust bestmatch' && suggesthistorical = Just historicalamountstr-                           | n > 1             = Just balancingamountstr-                           | otherwise         = Nothing+          defamountstr | isJust commandlineamt                  = commandlineamt+                       | isJust bestmatch' && suggesthistorical = Just historicalamountstr+                       | n > 1                                  = Just balancingamountstr+                       | otherwise                              = Nothing               where+                commandlineamt      = headMay defargs'                 historicalamountstr = showMixedAmountWithPrecision p $ pamount $ fromJust bestmatch'                 balancingamountstr  = showMixedAmountWithPrecision p $ negate $ sum $ map pamount enteredrealps                 -- what should this be ?@@ -146,22 +203,26 @@                 -- 5 3 or 4, whichever would show the most decimal places ?                 -- I think 1 or 4, whichever would show the most decimal places                 p = maxprecisionwithpoint-      amountstr <- runInteractionDefault $ askFor (printf "amount  %d" n) defaultamountstr validateamount+          defargs'' = tailDef [] defargs'+      amt <- runInteractionDefault $ askFor (printf "amount  %d" n) defamountstr validateamount+      when (amt=="<") $ throwIO RestartEntryException+      let (amountstr,comment) = (strip a, strip $ dropWhile (==';') b) where (a,b) = break (==';') amt       let a  = fromparse $ runParser (amountp <|> return missingamt) ctx     "" amountstr           a' = fromparse $ runParser (amountp <|> return missingamt) nullctx "" amountstr-          wasdefaultamtused = Just (showAmount a) == defaultamountstr-          defaultcommodityadded | acommodity a == acommodity a' = Nothing+          wasdefamtused = Just (showAmount a) == defamountstr+          defcommodityadded | acommodity a == acommodity a' = Nothing                                 | otherwise                     = Just $ acommodity a           p = nullposting{paccount=stripbrackets account                          ,pamount=mixed a+                         ,pcomment=comment                          ,ptype=postingtype account                          }-          st' = if wasdefaultamtused+          st' = if wasdefamtused                  then st                  else st{psHistory=historicalps', psSuggestHistoricalAmount=False}-      when (isJust defaultcommodityadded) $-           liftIO $ hPutStrLn stderr $ printf "using default commodity (%s)" (fromJust defaultcommodityadded)-      getPostings st' (enteredps ++ [p])+      when (isJust defcommodityadded) $+           liftIO $ hPutStrLn stderr $ printf "using default commodity (%s)" (fromJust defcommodityadded)+      getPostingsLoop st' (enteredps ++ [p]) defargs''     where       j = psJournal st       historicalps = psHistory st@@ -174,17 +235,18 @@       postingtype ('[':_) = BalancedVirtualPosting       postingtype ('(':_) = VirtualPosting       postingtype _ = RegularPosting-      stripbrackets = dropWhile (`elem` "([") . reverse . dropWhile (`elem` "])") . reverse       validateamount = Just $ \s -> (null s && not (null enteredrealps))-                                   || isRight (runParser (amountp >> many spacenonewline >> eof) ctx "" s)+                                    || s == "<"+                                    || (isRight (runParser (amountp >> many spacenonewline >> optional (char ';' >> many anyChar) >> eof) ctx "" s)+                                        && s /= ".")  -- | Prompt for and read a string value, optionally with a default value -- and a validator. A validator causes the prompt to repeat until the--- input is valid. May also raise an EOF exception if control-d is pressed.+-- input is valid. May also raise an EOF exception if control-d or control-c is pressed. askFor :: String -> Maybe String -> Maybe (String -> Bool) -> InputT IO String askFor prompt def validator = do   l <- fmap (maybe eofErr id)-            $ getInputLine $ prompt ++ maybe "" showdef def ++ ": "+            $ getInputLine $ prompt ++ " ? " ++ maybe "" showdef def ++ ": "   let input = if null l then fromMaybe l def else l   case validator of     Just valid -> if valid input@@ -192,8 +254,9 @@                    else askFor prompt def validator     Nothing -> return input     where-        showdef s = " [" ++ s ++ "]"-        eofErr = C.throw $ mkIOError eofErrorType "end of input" Nothing Nothing+        showdef "" = ""+        showdef s = "[" ++ s ++ "]"+        eofErr = E.throw $ mkIOError eofErrorType "end of input" Nothing Nothing  -- | Append this transaction to the journal's file, and to the journal's -- transaction list.@@ -224,8 +287,10 @@ registerFromString s = do   d <- getCurrentDay   j <- readJournal' s-  return $ postingsReportAsText opts $ postingsReport opts (queryFromOpts d opts) j-      where opts = defreportopts{empty_=True}+  return $ postingsReportAsText opts $ postingsReport ropts (queryFromOpts d ropts) j+      where+        ropts = defreportopts{empty_=True}+        opts = defcliopts{reportopts_=ropts}  -- | Return a similarity measure, from 0 to 1, for two strings. -- This is Simon White's letter pairs algorithm from@@ -285,3 +350,11 @@                            -- can contain spaces.                         $ \s -> return $ map simpleCompletion                                         $ filter (s `isPrefixOf`) cc++capitalize :: String -> String+capitalize "" = ""+capitalize (c:cs) = toUpper c : cs++headTailDef :: a -> [a] -> (a,[a])+headTailDef defhead as = (headDef defhead as, tailDef [] as)+
Hledger/Cli/Histogram.hs view
@@ -34,7 +34,7 @@       i = intervalFromOpts opts       interval | i == NoInterval = Days 1                | otherwise = i-      span = queryDateSpan (effective_ opts) q `orDatesFrom` journalDateSpan j+      span = queryDateSpan (date2_ opts) q `orDatesFrom` journalDateSpan j       spans = filter (DateSpan Nothing Nothing /=) $ splitSpan interval span       spanps = [(s, filter (isPostingInDateSpan s) ps) | s <- spans]       -- same as Register
Hledger/Cli/Options.hs view
@@ -7,7 +7,7 @@  module Hledger.Cli.Options where-import Control.Exception as C+import qualified Control.Exception as C import Data.List import Data.List.Split import Data.Maybe@@ -19,7 +19,7 @@ import System.Directory import System.Environment import Test.HUnit-import Text.ParserCombinators.Parsec+import Text.ParserCombinators.Parsec as P import Text.Printf  import Hledger@@ -143,7 +143,7 @@  ,flagNone ["cost","B"]      (\opts -> setboolopt "cost" opts) "report cost of commodities"  ,flagReq  ["depth"]         (\s opts -> Right $ setopt "depth" s opts) "N" "hide accounts/transactions deeper than this"  ,flagReq  ["display","d"]   (\s opts -> Right $ setopt "display" s opts) "DISPLAYEXP" "show only transactions matching the expression, which is 'dOP[DATE]' where OP is <, <=, =, >=, >"- ,flagNone ["effective"]     (\opts -> setboolopt "effective" opts) "use transactions' effective dates, if any"+ ,flagNone ["date2","aux-date","effective"]     (\opts -> setboolopt "date2" opts) "use transactions' secondary dates, if any"  ,flagNone ["empty","E"]     (\opts -> setboolopt "empty" opts) "show empty/zero things which are normally elided"  ,flagNone ["real","R"]      (\opts -> setboolopt "real" opts) "report only on real (non-virtual) transactions"  ]@@ -189,7 +189,7 @@  ,modeGroupFlags = Group {      groupUnnamed = [       flagNone ["flat"] (\opts -> setboolopt "flat" opts) "show full account names, unindented"-     ,flagReq ["drop"] (\s opts -> Right $ setopt "drop" s opts) "N" "with --flat, omit this many leading account name components"+     ,flagReq  ["drop"] (\s opts -> Right $ setopt "drop" s opts) "N" "with --flat, omit this many leading account name components"      ,flagReq  ["format"] (\s opts -> Right $ setopt "format" s opts) "FORMATSTR" "use this custom line format"      ,flagNone ["no-elide"] (\opts -> setboolopt "no-elide" opts) "no eliding at all, stronger than --empty"      ,flagNone ["no-total"] (\opts -> setboolopt "no-total" opts) "don't show the final total"@@ -213,7 +213,10 @@   modeHelp = "(or postings) show matched postings and running total"  ,modeArgs = ([], Just commandargsflag)  ,modeGroupFlags = Group {-     groupUnnamed = []+     groupUnnamed = [+      flagOpt (show defaultWidthWithFlag) ["width","w"] (\s opts -> Right $ setopt "width" s opts) "N" "increase or set the output width (default: 80)"+     ,flagNone ["related","r"] (\opts -> setboolopt "related" opts) "show the other postings in the transactions of those that would have been shown"+     ]     ,groupHidden = []     ,groupNamed = [(generalflagstitle, generalflags1)]     }@@ -293,6 +296,7 @@     ,alias_           :: [String]     ,debug_           :: Bool     ,no_new_accounts_ :: Bool           -- add+    ,width_           :: Maybe String   -- register     ,reportopts_      :: ReportOpts  } deriving (Show) @@ -305,6 +309,7 @@     def     def     def+    def  instance Default CliOpts where def = defcliopts @@ -322,6 +327,7 @@              ,alias_           = map stripquotes $ listofstringopt "alias" rawopts              ,debug_           = boolopt "debug" rawopts              ,no_new_accounts_ = boolopt "no-new-accounts" rawopts -- add+             ,width_           = maybestringopt "width" rawopts    -- register              ,reportopts_ = defreportopts {                              begin_     = maybesmartdateopt d "begin" rawopts                             ,end_       = maybesmartdateopt d "end" rawopts@@ -331,7 +337,7 @@                             ,cost_      = boolopt "cost" rawopts                             ,depth_     = maybeintopt "depth" rawopts                             ,display_   = maybedisplayopt d rawopts-                            ,effective_ = boolopt "effective" rawopts+                            ,date2_     = boolopt "date2" rawopts                             ,empty_     = boolopt "empty" rawopts                             ,no_elide_  = boolopt "no-elide" rawopts                             ,real_      = boolopt "real" rawopts@@ -344,6 +350,7 @@                             ,quarterly_ = boolopt "quarterly" rawopts                             ,yearly_    = boolopt "yearly" rawopts                             ,format_    = maybestringopt "format" rawopts+                            ,related_   = boolopt "related" rawopts  -- register                             ,query_     = unwords $ listofstringopt "args" rawopts                             }              }@@ -441,10 +448,13 @@   case formatFromOpts ropts of     Left err -> optserror $ "could not parse format option: "++err     Right _ -> return ()+  case widthFromOpts opts of+    Left err -> optserror $ "could not parse width option: "++err+    Right _ -> return ()   return opts --- | Parse any format option provided, possibly raising an error, or get--- the default value.+-- | Parse the format option if provided, possibly returning an error,+-- otherwise get the default value. formatFromOpts :: ReportOpts -> Either String [FormatString] formatFromOpts = maybe (Right defaultBalanceFormatString) parseFormatString . format_ @@ -456,6 +466,35 @@     , FormatField True (Just 2) Nothing DepthSpacerField     , FormatField True Nothing Nothing AccountField     ]++data OutputWidth = TotalWidth Width | FieldWidths [Width] deriving Show+data Width = Width Int | Auto deriving Show++defaultWidth         = 80+defaultWidthWithFlag = 120++-- | Parse the width option if provided, possibly returning an error,+-- otherwise get the default value.+widthFromOpts :: CliOpts -> Either String OutputWidth+widthFromOpts CliOpts{width_=Nothing} = Right $ TotalWidth $ Width defaultWidth+widthFromOpts CliOpts{width_=Just ""} = Right $ TotalWidth $ Width defaultWidthWithFlag+widthFromOpts CliOpts{width_=Just s}  = parseWidth s++parseWidth :: String -> Either String OutputWidth+parseWidth s = case (runParser outputwidth () "(unknown)") s of+    Left  e -> Left $ show e+    Right x -> Right x++outputwidth :: GenParser Char st OutputWidth+outputwidth =+  try (do w <- width+          ws <- many1 (char ',' >> width)+          return $ FieldWidths $ w:ws)+  <|> TotalWidth `fmap` width++width :: GenParser Char st Width+width = (string "auto" >> return Auto)+    <|> (Width . read) `fmap` many1 digit  -- | Get the (tilde-expanded, absolute) journal file path from options, an environment variable, or a default. journalFilePathFromOpts :: CliOpts -> IO String
Hledger/Cli/Register.hs view
@@ -7,7 +7,7 @@ module Hledger.Cli.Register (   register  ,postingsReportAsText- ,showPostingWithBalanceForVty+ -- ,showPostingWithBalanceForVty  ,tests_Hledger_Cli_Register ) where @@ -24,12 +24,12 @@  -- | Print a (posting) register report. register :: CliOpts -> Journal -> IO ()-register CliOpts{reportopts_=ropts} j = do+register opts@CliOpts{reportopts_=ropts} j = do   d <- getCurrentDay-  putStr $ postingsReportAsText ropts $ postingsReport ropts (queryFromOpts d ropts) j+  putStr $ postingsReportAsText opts $ postingsReport ropts (queryFromOpts d ropts) j  -- | Render a register report as plain text suitable for console output.-postingsReportAsText :: ReportOpts -> PostingsReport -> String+postingsReportAsText :: CliOpts -> PostingsReport -> String postingsReportAsText opts = unlines . map (postingsReportItemAsText opts) . snd  tests_postingsReportAsText = [@@ -38,35 +38,50 @@     j <- readJournal'       "2009/01/01 * медвежья шкура\n  расходы:покупки  100\n  актив:наличные\n"     let opts = defreportopts-    (postingsReportAsText opts $ postingsReport opts (queryFromOpts (parsedate "2008/11/26") opts) j) `is` unlines-      ["2009/01/01 медвежья шкура       расходы:покупки                 100          100"-      ,"                                актив:наличные                 -100            0"]+    (postingsReportAsText defcliopts $ postingsReport opts (queryFromOpts (parsedate "2008/11/26") opts) j) `is` unlines+      ["2009/01/01 медвежья шкура       расходы:покупки                100           100"+      ,"                                актив:наличные                -100             0"]  ] --- | Render one register report line item as plain text. Eg:+-- | Render one register report line item as plain text. Layout is like so: -- @--- date (10)  description (20)     account (22)            amount (11)  balance (12)--- DDDDDDDDDD dddddddddddddddddddd aaaaaaaaaaaaaaaaaaaaaa  AAAAAAAAAAA AAAAAAAAAAAA--- ^ displayed for first postings^---   only, otherwise blank+-- <----------------------------- width (default: 80) ---------------------------->+-- date (10)  description (50%)     account (50%)         amount (12)  balance (12)+-- DDDDDDDDDD dddddddddddddddddddd  aaaaaaaaaaaaaaaaaaa  AAAAAAAAAAAA  AAAAAAAAAAAA+--+-- date and description are shown for the first posting of a transaction only. -- @-postingsReportItemAsText :: ReportOpts -> PostingsReportItem -> String-postingsReportItemAsText _ (dd, p, b) = concatTopPadded [datedesc, pstr, " ", bal]+postingsReportItemAsText :: CliOpts -> PostingsReportItem -> String+postingsReportItemAsText opts (mdate, mdesc, p, b) =+  concatTopPadded [date, " ", desc, "  ", acct, "  ", amt, "  ", bal]     where-      datedesc = case dd of Nothing -> replicate datedescwidth ' '-                            Just (da, de) -> printf "%s %s " date desc-                                where-                                  date = showDate da-                                  desc = printf ("%-"++(show descwidth)++"s") $ elideRight descwidth de :: String-          where-            descwidth = datedescwidth - datewidth - 2-            datedescwidth = 32-            datewidth = 10-      pstr = showPostingForRegister p-      bal = padleft 12 (showMixedAmountWithoutPrice b)+      totalwidth = case widthFromOpts opts of+           Left _                       -> defaultWidth -- shouldn't happen+           Right (TotalWidth (Width w)) -> w+           Right (TotalWidth Auto)      -> defaultWidth -- XXX+           Right (FieldWidths _)        -> defaultWidth -- XXX+      datewidth = 10+      amtwidth = 12+      balwidth = 12+      remaining = totalwidth - (datewidth + 1 + 2 + amtwidth + 2 + balwidth)+      (descwidth, acctwidth) | even r    = (r', r')+                             | otherwise = (r', r'+1)+        where r = remaining - 2+              r' = r `div` 2+      date = maybe (replicate datewidth ' ') (printf ("%-"++show datewidth++"s") . showDate) mdate+      desc = maybe (replicate descwidth ' ') (printf ("%-"++show descwidth++"s") . take descwidth . elideRight descwidth) mdesc+      acct = printf ("%-"++(show acctwidth)++"s") a+        where+          a = bracket $ elideAccountName awidth $ paccount p+          (bracket, awidth) = case ptype p of+                               BalancedVirtualPosting -> (\s -> "["++s++"]", acctwidth-2)+                               VirtualPosting -> (\s -> "("++s++")", acctwidth-2)+                               _ -> (id,acctwidth)+      amt = padleft amtwidth $ showMixedAmountWithoutPrice $ pamount p+      bal = padleft balwidth $ showMixedAmountWithoutPrice b  -- XXX-showPostingWithBalanceForVty showtxninfo p b = postingsReportItemAsText defreportopts $ mkpostingsReportItem showtxninfo p b+-- showPostingWithBalanceForVty showtxninfo p b = postingsReportItemAsText defreportopts $ mkpostingsReportItem showtxninfo p b  tests_Hledger_Cli_Register :: Test tests_Hledger_Cli_Register = TestList
Hledger/Cli/Tests.hs view
@@ -1,3 +1,5 @@+-- {-# OPTIONS_GHC -F -pgmF htfpp #-}+{-# LANGUAGE CPP #-} {- |  A simple test runner for hledger's built-in unit tests.@@ -13,9 +15,19 @@ import Hledger import Hledger.Cli +#ifdef TESTS --- | Run unit tests and exit with success or failure.+import Test.Framework+import {-@ HTF_TESTS @-} Hledger.Read.JournalReader++-- | Run HTF unit tests and exit with success or failure. test' :: CliOpts -> IO ()+test' _opts = htfMain htf_importedTests++#else++-- | Run HUnit unit tests and exit with success or failure.+test' :: CliOpts -> IO () test' opts = do   results <- runTests opts   if errors results > 0 || failures results > 0@@ -39,3 +51,5 @@  -- | All or pattern-matched tests, in the original suites to show hierarchical names. hierarchicalTests opts = filterTests (matchesAccount (queryFromOpts nulldate $ reportopts_ opts) . testName) tests_Hledger_Cli++#endif
Hledger/Cli/Utils.hs view
@@ -34,7 +34,7 @@ import System.Time (ClockTime, getClockTime, diffClockTimes, TimeDiff(TimeDiff)) import Test.HUnit import Text.Printf-#if __GLASGOW_HASKELL__ >= 706+#if MIN_VERSION_directory(1,2,0) import System.Time (ClockTime(TOD)) import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds) #endif@@ -103,13 +103,12 @@ fileModificationTime f     | null f = getClockTime     | otherwise = (do--- getModificationTime returned a ClockTime till GHC 7.6 (directory 1.2), now it's UTCTime-#if __GLASGOW_HASKELL__ < 706-        clo <- getModificationTime f-#else+#if MIN_VERSION_directory(1,2,0)         utc <- getModificationTime f         let nom = utcTimeToPOSIXSeconds utc         let clo = TOD (read $ takeWhile (`elem` "0123456789") $ show nom) 0 -- XXX read+#else+        clo <- getModificationTime f #endif         return clo         )@@ -142,7 +141,7 @@ -- indicating whether we did anything. writeFileWithBackupIfChanged :: FilePath -> String -> IO Bool writeFileWithBackupIfChanged f t = do-  s <- readFile f+  s <- readFile' f   if t == s then return False             else backUpFile f >> writeFile f t >> return True @@ -152,7 +151,7 @@ writeFileWithBackup f t = backUpFile f >> writeFile f t  readFileStrictly :: FilePath -> IO String-readFileStrictly f = readFile f >>= \s -> C.evaluate (length s) >> return s+readFileStrictly f = readFile' f >>= \s -> C.evaluate (length s) >> return s  -- | Back up this file with a (incrementing) numbered suffix, or give an error. backUpFile :: FilePath -> IO ()
hledger.cabal view
@@ -1,6 +1,6 @@ name:           hledger -- also in cpp-options below-version: 0.19.3+version: 0.20 category:       Finance synopsis:       The main command-line interface for the hledger accounting tool. description:@@ -18,7 +18,7 @@ homepage:       http://hledger.org bug-reports:    http://code.google.com/p/hledger/issues stability:      beta-tested-with:    GHC==7.0.4, GHC==7.2.2, GHC==7.4.1, GHC==7.6.1+tested-with:    GHC==7.2.2, GHC==7.4.2, GHC==7.6.1 cabal-version:  >= 1.8 build-type:     Simple -- data-dir:       data@@ -41,7 +41,7 @@     Default:       True  library-  cpp-options:    -DVERSION="0.19.3"+  cpp-options:    -DVERSION="0.20"   ghc-options:    -W   -- should be the same as below   exposed-modules:@@ -62,7 +62,7 @@                   Hledger.Cli.Stats   -- should be the same as below   build-depends:-                  hledger-lib == 0.19.3+                  hledger-lib == 0.20                  ,base >= 4.3 && < 5                  -- ,cabal-file-th                  ,containers@@ -110,13 +110,13 @@                   Hledger.Cli.Print                   Hledger.Cli.Register                   Hledger.Cli.Stats-  cpp-options:    -DVERSION="0.19.3"+  cpp-options:    -DVERSION="0.20"   ghc-options:    -W   if flag(threaded)        ghc-options:   -threaded  -- should be the same as above   build-depends:-                  hledger-lib == 0.19.3+                  hledger-lib == 0.20                  ,base >= 4.3 && < 5                  ,containers                  ,cmdargs >= 0.10 && < 0.11