input validation for the add command, and some doctests

This commit is contained in:
Simon Michael 2009-04-10 07:10:21 +00:00
parent c521dc0bc2
commit ace0d7fe79

View File

@ -25,48 +25,57 @@ add opts args l
hPutStrLn stderr ("Please enter one or more transactions, which will be added to your ledger file.\n\ hPutStrLn stderr ("Please enter one or more transactions, which will be added to your ledger file.\n\
\A blank account or amount ends a transaction, control-d to finish.") \A blank account or amount ends a transaction, control-d to finish.")
ts <- getAndAddTransactions l ts <- getAndAddTransactions l
putStrLn $ printf "\nAdded %d transactions to %s ." (length ts) (filepath $ rawledger l) hPutStrLn stderr $ printf "\nAdded %d transactions to %s ." (length ts) (filepath $ rawledger l)
-- | Read a number of ledger transactions from the command line, -- | Read a number of ledger transactions from the command line,
-- prompting, validating, displaying and appending them to the ledger file. -- prompting, validating, displaying and appending them to the ledger file.
getAndAddTransactions :: Ledger -> IO [LedgerTransaction] getAndAddTransactions :: Ledger -> IO [LedgerTransaction]
getAndAddTransactions l = (do getAndAddTransactions l = (do
today <- getCurrentDay today <- getCurrentDay
date <- liftM (fixSmartDate today . fromparse . parse smartdate "" . lowercase) datestr <- askFor "date" (Just $ showDate today)
$ askFor "date" (Just $ showDate today) (Just $ \s -> null s ||
description <- askFor "description" Nothing (isRight $ parse (smartdate >> many spacenonewline >> eof) "" $ lowercase s))
let date = fixSmartDate today $ fromparse $ parseSmartDate datestr
description <- askFor "description" Nothing (Just $ not . null)
ps <- getPostings [] ps <- getPostings []
let t = nullledgertxn{ltdate=date let t = nullledgertxn{ltdate=date
,ltstatus=False ,ltstatus=False
,ltdescription=description ,ltdescription=description
,ltpostings=ps ,ltpostings=ps
} }
registerFromString (show t) >>= putStrLn -- a final validation, will raise an error if not parseable and balanced
putStrLn =<< registerFromString (show t)
appendToLedgerFile l $ show t appendToLedgerFile l $ show t
liftM (t:) (getAndAddTransactions l) liftM (t:) (getAndAddTransactions l)
) `catch` (\e -> if isEOFError e then return [] else ioError e) ) `catch` (\e -> if isEOFError e then return [] else ioError e)
where parseSmartDate = parse smartdate "" . lowercase
-- | Read one or more postings interactively. -- | Read two or more postings interactively.
getPostings :: [Posting] -> IO [Posting] getPostings :: [Posting] -> IO [Posting]
getPostings prevps = do getPostings prevps = do
account <- askFor "account" Nothing account <- askFor "account" Nothing (Just $ \s -> not $ null s && (length prevps < 2))
if null account if null account
then return prevps then return prevps
else do else do
amount <- liftM (fromparse . parse (someamount <|> return missingamt) "") amount <- liftM (fromparse . parse (someamount <|> return missingamt) "")
$ askFor "amount" Nothing $ askFor "amount" Nothing (Just $ \s -> not $ null s && null prevps)
let p = nullrawposting{paccount=account,pamount=amount} let p = nullrawposting{paccount=account,pamount=amount}
if amount == missingamt if amount == missingamt
then return $ prevps ++ [p] then return $ prevps ++ [p]
else getPostings $ prevps ++ [p] else getPostings $ prevps ++ [p]
-- | Prompt and read a string value, possibly with a default. -- | Prompt and read a string value, possibly with a default and a validator.
askFor :: String -> Maybe String -> IO String -- A validator will cause the prompt to repeat until the input is valid.
askFor prompt def = do askFor :: String -> Maybe String -> Maybe (String -> Bool) -> IO String
askFor prompt def validator = do
hPutStr stderr $ prompt ++ (maybe "" showdef def) ++ ": " hPutStr stderr $ prompt ++ (maybe "" showdef def) ++ ": "
hFlush stderr hFlush stderr
l <- getLine l <- getLine
return $ if null l then fromMaybe l def else l let input = if null l then fromMaybe l def else l
case validator of
Just valid -> if valid input then return input else askFor prompt def validator
Nothing -> return input
where showdef s = " [" ++ s ++ "]" where showdef s = " [" ++ s ++ "]"
-- | Append a string of transactions to the ledger's file, ensuring proper -- | Append a string of transactions to the ledger's file, ensuring proper
@ -80,7 +89,9 @@ appendToLedgerFile l s =
where where
f = filepath $ rawledger l f = filepath $ rawledger l
t = rawledgertext l t = rawledgertext l
sep = replicate (2 - min 2 (length nls)) '\n' where nls = takeWhile (=='\n') $ reverse t sep | null $ strip t = ""
| otherwise = replicate (2 - min 2 (length lastnls)) '\n'
where lastnls = takeWhile (=='\n') $ reverse t
-- | Convert a string of ledger data into a register report. -- | Convert a string of ledger data into a register report.
registerFromString :: String -> IO String registerFromString :: String -> IO String
@ -89,3 +100,21 @@ registerFromString s = do
l <- ledgerFromStringWithOpts [] [] now s l <- ledgerFromStringWithOpts [] [] now s
return $ showRegisterReport [] [] l return $ showRegisterReport [] [] l
{- doctests
@
$ echo "2009/13/1"|hledger -f /dev/null add 2>&1|tail -2|head -1|sed -e's/\[[^]]*\]//g' # a bad date is not accepted
date : date :
@
@
$ echo|hledger -f /dev/null add 2>&1|tail -2|head -1|sed -e's/\[[^]]*\]//g' # a blank date is ok
date : description:
@
@
$ (echo;echo)|hledger -f /dev/null add 2>&1|tail -2|head -1|sed -e's/\[[^]]*\]//g' # a blank description should fail
date : description: description:
@
-}