do IO date parsing up front, and support (english) month names
This commit is contained in:
parent
1c60514973
commit
6c88197c6a
@ -483,92 +483,99 @@ ledgerfromtimelog = do
|
|||||||
|
|
||||||
-- misc parsing
|
-- misc parsing
|
||||||
{-|
|
{-|
|
||||||
Parse a date in any of the formats allowed in ledger's period expressions:
|
Parse a date in any of the formats allowed in ledger's period expressions,
|
||||||
|
and maybe some others:
|
||||||
|
|
||||||
> 2004
|
> 2004
|
||||||
> 2004/10
|
> 2004/10
|
||||||
> 2004/10/1
|
> 2004/10/1
|
||||||
> 10/1
|
> 10/1
|
||||||
> october
|
> 21
|
||||||
> oct
|
> october, oct
|
||||||
> this week # or day, month, quarter, year
|
> this/next/last week/day/month/quarter/year
|
||||||
> next week
|
> yesterday, today, tomorrow
|
||||||
> last week
|
|
||||||
|
Note: only recognises month names in lowercase.
|
||||||
-}
|
-}
|
||||||
smartdate :: Parser (String,String,String)
|
smartdate :: Parser (String,String,String)
|
||||||
smartdate = do
|
smartdate = do
|
||||||
(y,m,d) <- (
|
(y,m,d) <- choice [
|
||||||
try ymd
|
try ymd
|
||||||
<|> try ym
|
,try ym
|
||||||
<|> try md
|
,try md
|
||||||
<|> try y
|
,try y
|
||||||
<|> try d
|
,try d
|
||||||
-- <|> try month
|
,try month
|
||||||
-- <|> try mon
|
,try mon
|
||||||
-- <|> try today
|
-- ,try today
|
||||||
-- <|> try yesterday
|
-- ,try yesterday
|
||||||
-- <|> try tomorrow
|
-- ,try tomorrow
|
||||||
-- <|> try thiswhatever
|
-- ,try thiswhatever
|
||||||
-- <|> try nextwhatever
|
-- ,try nextwhatever
|
||||||
-- <|> try lastwhatever
|
-- ,try lastwhatever
|
||||||
)
|
]
|
||||||
return $ (y,m,d)
|
return $ (y,m,d)
|
||||||
|
|
||||||
datesep = oneOf "/-."
|
datesep = oneOf "/-."
|
||||||
|
|
||||||
ymd :: Parser (String,String,String)
|
ymd :: Parser (String,String,String)
|
||||||
ymd = do
|
ymd = do
|
||||||
y <- many digit
|
y <- many1 digit
|
||||||
datesep
|
datesep
|
||||||
m <- many digit
|
m <- many1 digit
|
||||||
guard (read m <= 12)
|
guard (read m <= 12)
|
||||||
datesep
|
datesep
|
||||||
d <- many digit
|
d <- many1 digit
|
||||||
guard (read d <= 31)
|
guard (read d <= 31)
|
||||||
return (y,m,d)
|
return (y,m,d)
|
||||||
|
|
||||||
ym :: Parser (String,String,String)
|
ym :: Parser (String,String,String)
|
||||||
ym = do
|
ym = do
|
||||||
y <- many digit
|
y <- many1 digit
|
||||||
guard (read y > 12)
|
guard (read y > 12)
|
||||||
datesep
|
datesep
|
||||||
m <- many digit
|
m <- many1 digit
|
||||||
guard (read m <= 12)
|
guard (read m <= 12)
|
||||||
return (y,m,"1")
|
return (y,m,"1")
|
||||||
|
|
||||||
y :: Parser (String,String,String)
|
y :: Parser (String,String,String)
|
||||||
y = do
|
y = do
|
||||||
y <- many digit
|
y <- many1 digit
|
||||||
guard (read y >= 1000)
|
guard (read y >= 1000)
|
||||||
return (y,"1","1")
|
return (y,"1","1")
|
||||||
|
|
||||||
d :: Parser (String,String,String)
|
d :: Parser (String,String,String)
|
||||||
d = do
|
d = do
|
||||||
d <- many digit
|
d <- many1 digit
|
||||||
guard (read d <= 31)
|
guard (read d <= 31)
|
||||||
return ("","",d)
|
return ("","",d)
|
||||||
|
|
||||||
-- | Parse a M/D string as ("",M,D), year will be filled in later
|
-- | Parse a M/D string as ("",M,D), year will be filled in later
|
||||||
md :: Parser (String,String,String)
|
md :: Parser (String,String,String)
|
||||||
md = do
|
md = do
|
||||||
m <- many digit
|
m <- many1 digit
|
||||||
guard (read m <= 12)
|
guard (read m <= 12)
|
||||||
datesep
|
datesep
|
||||||
d <- many digit
|
d <- many1 digit
|
||||||
guard (read d <= 31)
|
guard (read d <= 31)
|
||||||
return ("",m,d)
|
return ("",m,d)
|
||||||
|
|
||||||
-- | Parse a flexible date string to a Date with awareness of the current
|
months = ["january","february","march","april","may","june",
|
||||||
-- time, or raise an error.
|
"july","august","september","october","november","december"]
|
||||||
smartparsedate :: String -> IO Date
|
|
||||||
smartparsedate s = do
|
mons = ["jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"]
|
||||||
let (y,m,d) = fromparse $ parsewith smartdate s
|
|
||||||
(thisy,thism,_) <- today >>= return . dateComponents
|
month :: Parser (String,String,String)
|
||||||
let (y',m',d') = case (y,m,d) of
|
month = do
|
||||||
("","",d) -> (show thisy,show thism,d)
|
m <- choice $ map string months
|
||||||
("",m,d) -> (show thisy,m,d)
|
let i = maybe 0 (+1) $ (map toLower m) `elemIndex` months
|
||||||
otherwise -> (y,m,d)
|
return ("",show i,"1")
|
||||||
return $ parsedate $ printf "%04s/%02s/%02s" y' m' d'
|
|
||||||
|
mon :: Parser (String,String,String)
|
||||||
|
mon = do
|
||||||
|
m <- choice $ map string mons
|
||||||
|
let i = maybe 0 (+1) $ (map toLower m) `elemIndex` mons
|
||||||
|
return ("",show i,"1")
|
||||||
|
|
||||||
type TransactionMatcher = Transaction -> Bool
|
type TransactionMatcher = Transaction -> Bool
|
||||||
|
|
||||||
|
|||||||
61
Options.hs
61
Options.hs
@ -4,12 +4,12 @@ import System
|
|||||||
import System.Console.GetOpt
|
import System.Console.GetOpt
|
||||||
import System.Directory
|
import System.Directory
|
||||||
import Text.Printf
|
import Text.Printf
|
||||||
import Ledger.Parse (smartparsedate)
|
import Ledger.Parse
|
||||||
import Ledger.Dates
|
import Ledger.Dates
|
||||||
import Ledger.Utils
|
import Ledger.Utils
|
||||||
|
|
||||||
|
|
||||||
usage opts = usageInfo usagehdr options ++ usageftr
|
usage = usageInfo usagehdr options ++ usageftr
|
||||||
|
|
||||||
negativePatternChar opts
|
negativePatternChar opts
|
||||||
| OptionsAnywhere `elem` opts = '^'
|
| OptionsAnywhere `elem` opts = '^'
|
||||||
@ -81,15 +81,42 @@ versionno = "0.3pre"
|
|||||||
version = printf "hledger version %s \n" versionno :: String
|
version = printf "hledger version %s \n" versionno :: String
|
||||||
|
|
||||||
-- | Parse the command-line arguments into ledger options, ledger command
|
-- | Parse the command-line arguments into ledger options, ledger command
|
||||||
-- name, and ledger command arguments
|
-- name, and ledger command arguments. Also any dates in the options are
|
||||||
|
-- converted to full YYYY/MM/DD format, while we are in the IO monad
|
||||||
|
-- and can get the current time.
|
||||||
parseArguments :: IO ([Opt], String, [String])
|
parseArguments :: IO ([Opt], String, [String])
|
||||||
parseArguments = do
|
parseArguments = do
|
||||||
args <- getArgs
|
args <- getArgs
|
||||||
let order = if "--options-anywhere" `elem` args then Permute else RequireOrder
|
let order = if "--options-anywhere" `elem` args then Permute else RequireOrder
|
||||||
case (getOpt order options args) of
|
case (getOpt order options args) of
|
||||||
(opts,cmd:args,[]) -> return (opts, cmd, args)
|
(opts,cmd:args,[]) -> do {opts' <- fixDates opts; return (opts',cmd,args)}
|
||||||
(opts,[],[]) -> return (opts, [], [])
|
(opts,[],[]) -> do {opts' <- fixDates opts; return (opts',[],[])}
|
||||||
(opts,_,errs) -> ioError (userError (concat errs ++ usage opts))
|
(opts,_,errs) -> ioError (userError (concat errs ++ usage))
|
||||||
|
|
||||||
|
-- | Convert any fuzzy/relative dates within these option values to
|
||||||
|
-- explicit ones, based on today's date.
|
||||||
|
fixDates :: [Opt] -> IO [Opt]
|
||||||
|
fixDates opts = do
|
||||||
|
ds <- today >>= return . dateComponents
|
||||||
|
return $ map (fixopt ds) opts
|
||||||
|
where
|
||||||
|
fixopt ds (Begin s) = Begin $ fixdate ds s
|
||||||
|
fixopt ds (End s) = End $ fixdate ds s
|
||||||
|
fixopt ds (Display s) = -- hacky
|
||||||
|
Display $ gsubRegexPRBy "\\[.+?\\]" fixbracketeddate s
|
||||||
|
where fixbracketeddate s = "[" ++ (fixdate ds $ init $ tail s) ++ "]"
|
||||||
|
fixopt _ o = o
|
||||||
|
|
||||||
|
-- | Convert a fuzzy date string to an explicit yyyy/mm/dd date, using the
|
||||||
|
-- provided today's date for defaults.
|
||||||
|
fixdate :: (Integer,Int,Int) -> String -> String
|
||||||
|
fixdate (thisy,thism,thisd) s = printf "%04s/%02s/%02s" y' m' d'
|
||||||
|
where
|
||||||
|
(y,m,d) = fromparse $ parsewith smartdate $ map toLower s
|
||||||
|
(y',m',d') = case (y,m,d) of
|
||||||
|
("","",d) -> (show thisy,show thism,d)
|
||||||
|
("",m,d) -> (show thisy,m,d)
|
||||||
|
otherwise -> (y,m,d)
|
||||||
|
|
||||||
-- | Get the ledger file path from options, an environment variable, or a default
|
-- | Get the ledger file path from options, an environment variable, or a default
|
||||||
ledgerFilePathFromOpts :: [Opt] -> IO String
|
ledgerFilePathFromOpts :: [Opt] -> IO String
|
||||||
@ -113,28 +140,30 @@ tildeExpand ('~':'/':xs) = getHomeDirectory >>= return . (++ ('/':xs))
|
|||||||
tildeExpand xs = return xs
|
tildeExpand xs = return xs
|
||||||
|
|
||||||
-- | Get the value of the begin date option, if any.
|
-- | Get the value of the begin date option, if any.
|
||||||
beginDateFromOpts :: [Opt] -> IO (Maybe Date)
|
beginDateFromOpts :: [Opt] -> Maybe Date
|
||||||
beginDateFromOpts opts =
|
beginDateFromOpts opts =
|
||||||
case beginopts of
|
if null beginopts
|
||||||
(x:_) -> smartparsedate (last beginopts) >>= return . Just
|
then Nothing
|
||||||
_ -> return Nothing
|
else Just $ parsedate $ printf "%04s/%02s/%02s" y m d
|
||||||
where
|
where
|
||||||
beginopts = concatMap getbegindate opts
|
beginopts = concatMap getbegindate opts
|
||||||
getbegindate (Begin s) = [s]
|
getbegindate (Begin s) = [s]
|
||||||
getbegindate _ = []
|
getbegindate _ = []
|
||||||
defaultdate = ""
|
defaultdate = ""
|
||||||
|
(y,m,d) = fromparse $ parsewith smartdate $ last beginopts
|
||||||
|
|
||||||
-- | Get the value of the end date option, if any.
|
-- | Get the value of the end date option, if any.
|
||||||
endDateFromOpts :: [Opt] -> IO (Maybe Date)
|
endDateFromOpts :: [Opt] -> Maybe Date
|
||||||
endDateFromOpts opts = do
|
endDateFromOpts opts =
|
||||||
case endopts of
|
if null endopts
|
||||||
(x:_) -> smartparsedate (last endopts) >>= return . Just
|
then Nothing
|
||||||
_ -> return Nothing
|
else Just $ parsedate $ printf "%04s/%02s/%02s" y m d
|
||||||
where
|
where
|
||||||
endopts = concatMap getenddate opts
|
endopts = concatMap getenddate opts
|
||||||
getenddate (End s) = [s]
|
getenddate (End s) = [s]
|
||||||
getenddate _ = []
|
getenddate _ = []
|
||||||
defaultdate = ""
|
defaultdate = ""
|
||||||
|
(y,m,d) = fromparse $ parsewith smartdate $ last endopts
|
||||||
|
|
||||||
-- | Get the value of the depth option, if any.
|
-- | Get the value of the depth option, if any.
|
||||||
depthFromOpts :: [Opt] -> Maybe Int
|
depthFromOpts :: [Opt] -> Maybe Int
|
||||||
@ -179,5 +208,5 @@ parseAccountDescriptionArgs opts args = (as, ds')
|
|||||||
testoptions order cmdline = putStr $
|
testoptions order cmdline = putStr $
|
||||||
case getOpt order options cmdline of
|
case getOpt order options cmdline of
|
||||||
(o,n,[] ) -> "options=" ++ show o ++ " args=" ++ show n
|
(o,n,[] ) -> "options=" ++ show o ++ " args=" ++ show n
|
||||||
(o,_,errs) -> concat errs ++ usage o
|
(o,_,errs) -> concat errs ++ usage
|
||||||
|
|
||||||
|
|||||||
21
Tests.hs
21
Tests.hs
@ -98,15 +98,18 @@ misc_tests = TestList [
|
|||||||
assertparseequal timelog1 (parsewith timelog timelog1_str)
|
assertparseequal timelog1 (parsewith timelog timelog1_str)
|
||||||
,
|
,
|
||||||
"smartparsedate" ~: do
|
"smartparsedate" ~: do
|
||||||
(thisyear,thismonth,thisday) <- today >>= return . dateComponents
|
ds@(y,m,d) <- today >>= return . dateComponents
|
||||||
d <- smartparsedate "1999-12-02"; assertequal (1999,12,2) (dateComponents d)
|
let str `gives` datestr = assertequal datestr (fixdate ds str)
|
||||||
d <- smartparsedate "1999.12.02"; assertequal (1999,12,2) (dateComponents d)
|
"1999-12-02" `gives` "1999/12/02"
|
||||||
d <- smartparsedate "1999/3/2"; assertequal (1999,3,2) (dateComponents d)
|
"1999.12.02" `gives` "1999/12/02"
|
||||||
d <- smartparsedate "2008/2"; assertequal (2008,2,1) (dateComponents d)
|
"1999/3/2" `gives` "1999/03/02"
|
||||||
d <- smartparsedate "20/2"; assertequal (20,2,1) (dateComponents d)
|
"2008/2" `gives` "2008/02/01"
|
||||||
d <- smartparsedate "4/2"; assertequal (thisyear,4,2) (dateComponents d)
|
"20/2" `gives` "0020/02/01"
|
||||||
d <- smartparsedate "1000"; assertequal (1000,1,1) (dateComponents d)
|
"1000" `gives` "1000/01/01"
|
||||||
d <- smartparsedate "2"; assertequal (thisyear,thismonth,2) (dateComponents d)
|
"4/2" `gives` (printf "%04d/04/02" y)
|
||||||
|
"2" `gives` (printf "%04d/%02d/02" y m)
|
||||||
|
"January" `gives` (printf "%04d/01/01" y)
|
||||||
|
"feb" `gives` (printf "%04d/02/01" y)
|
||||||
]
|
]
|
||||||
|
|
||||||
balancereportacctnames_tests = TestList
|
balancereportacctnames_tests = TestList
|
||||||
|
|||||||
10
hledger.hs
10
hledger.hs
@ -57,24 +57,24 @@ main = do
|
|||||||
run cmd opts args
|
run cmd opts args
|
||||||
where
|
where
|
||||||
run cmd opts args
|
run cmd opts args
|
||||||
| Help `elem` opts = putStr $ usage opts
|
| Help `elem` opts = putStr $ usage
|
||||||
| Version `elem` opts = putStr version
|
| Version `elem` opts = putStr version
|
||||||
| cmd `isPrefixOf` "balance" = parseLedgerAndDo opts args balance
|
| cmd `isPrefixOf` "balance" = parseLedgerAndDo opts args balance
|
||||||
| cmd `isPrefixOf` "print" = parseLedgerAndDo opts args print'
|
| cmd `isPrefixOf` "print" = parseLedgerAndDo opts args print'
|
||||||
| cmd `isPrefixOf` "register" = parseLedgerAndDo opts args register
|
| cmd `isPrefixOf` "register" = parseLedgerAndDo opts args register
|
||||||
| cmd `isPrefixOf` "test" = runtests opts args >> return ()
|
| cmd `isPrefixOf` "test" = runtests opts args >> return ()
|
||||||
| otherwise = putStr $ usage opts
|
| otherwise = putStr $ usage
|
||||||
|
|
||||||
-- | parse the user's specified ledger file and do some action with it
|
-- | parse the user's specified ledger file and do some action with it
|
||||||
-- (or report a parse error). This function makes the whole thing go.
|
-- (or report a parse error). This function makes the whole thing go.
|
||||||
parseLedgerAndDo :: [Opt] -> [String] -> ([Opt] -> [String] -> Ledger -> IO ()) -> IO ()
|
parseLedgerAndDo :: [Opt] -> [String] -> ([Opt] -> [String] -> Ledger -> IO ()) -> IO ()
|
||||||
parseLedgerAndDo opts args cmd = do
|
parseLedgerAndDo opts args cmd = do
|
||||||
b <- beginDateFromOpts opts
|
|
||||||
e <- endDateFromOpts opts
|
|
||||||
let runcmd = cmd opts args . cacheLedger apats . filterRawLedger b e dpats c r . canonicaliseAmounts costbasis
|
|
||||||
ledgerFilePathFromOpts opts >>= parseLedgerFile >>= either printParseError runcmd
|
ledgerFilePathFromOpts opts >>= parseLedgerFile >>= either printParseError runcmd
|
||||||
where
|
where
|
||||||
|
runcmd = cmd opts args . cacheLedger apats . filterRawLedger b e dpats c r . canonicaliseAmounts costbasis
|
||||||
(apats,dpats) = parseAccountDescriptionArgs opts args
|
(apats,dpats) = parseAccountDescriptionArgs opts args
|
||||||
|
b = beginDateFromOpts opts
|
||||||
|
e = endDateFromOpts opts
|
||||||
c = Cleared `elem` opts
|
c = Cleared `elem` opts
|
||||||
r = Real `elem` opts
|
r = Real `elem` opts
|
||||||
costbasis = CostBasis `elem` opts
|
costbasis = CostBasis `elem` opts
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user