support basic --period expressions, containing a single smart date

This commit is contained in:
Simon Michael 2008-11-27 06:29:29 +00:00
parent 1e7679176c
commit 57c31f5ab0
5 changed files with 133 additions and 47 deletions

View File

@ -53,7 +53,41 @@ elapsedSeconds t1 t2 = realToFrac $ diffUTCTime t1 t2
dayToUTC :: Day -> UTCTime dayToUTC :: Day -> UTCTime
dayToUTC d = localTimeToUTC utc (LocalTime d midnight) dayToUTC d = localTimeToUTC utc (LocalTime d midnight)
-- | Convert a fuzzy date string to an explicit yyyy/mm/dd string using -- | Convert a smart date string to a date span using the provided date as
-- reference point.
spanFromSmartDateString :: Day -> String -> DateSpan
spanFromSmartDateString refdate s = DateSpan (Just b) (Just e)
where
sdate = fromparse $ parsewith smartdate s
(ry,rm,rd) = toGregorian refdate
(b,e) = span sdate
span :: SmartDate -> (Day,Day)
span ("","","today") = (refdate, nextday refdate)
span ("","this","day") = (refdate, nextday refdate)
span ("","","yesterday") = (prevday refdate, refdate)
span ("","last","day") = (prevday refdate, refdate)
span ("","","tomorrow") = (nextday refdate, addDays 2 refdate)
span ("","next","day") = (nextday refdate, addDays 2 refdate)
span ("","last","week") = (prevweek refdate, thisweek refdate)
span ("","this","week") = (thisweek refdate, nextweek refdate)
span ("","next","week") = (nextweek refdate, startofweek $ addDays 14 refdate)
span ("","last","month") = (prevmonth refdate, thismonth refdate)
span ("","this","month") = (thismonth refdate, nextmonth refdate)
span ("","next","month") = (nextmonth refdate, startofmonth $ addGregorianMonthsClip 2 refdate)
span ("","last","quarter") = (prevquarter refdate, thisquarter refdate)
span ("","this","quarter") = (thisquarter refdate, nextquarter refdate)
span ("","next","quarter") = (nextquarter refdate, startofquarter $ addGregorianMonthsClip 6 refdate)
span ("","last","year") = (prevyear refdate, thisyear refdate)
span ("","this","year") = (thisyear refdate, nextyear refdate)
span ("","next","year") = (nextyear refdate, startofyear $ addGregorianYearsClip 2 refdate)
span ("","",d) = (day, nextday day) where day = fromGregorian ry rm (read d)
span ("",m,"") = (startofmonth day, nextmonth day) where day = fromGregorian ry (read m) 1
span ("",m,d) = (day, nextday day) where day = fromGregorian ry (read m) (read d)
span (y,"","") = (startofyear day, nextyear day) where day = fromGregorian (read y) 1 1
span (y,m,"") = (startofmonth day, nextmonth day) where day = fromGregorian (read y) (read m) 1
span (y,m,d) = (day, nextday day) where day = fromGregorian (read y) (read m) (read d)
-- | Convert a smart date string to an explicit yyyy/mm/dd string using
-- the provided date as reference point. -- the provided date as reference point.
fixSmartDateStr :: Day -> String -> String fixSmartDateStr :: Day -> String -> String
fixSmartDateStr t s = printf "%04d/%02d/%02d" y m d fixSmartDateStr t s = printf "%04d/%02d/%02d" y m d
@ -86,7 +120,10 @@ fixSmartDate refdate sdate = fix sdate
fix ("","this","year") = thisyear refdate fix ("","this","year") = thisyear refdate
fix ("","next","year") = nextyear refdate fix ("","next","year") = nextyear refdate
fix ("","",d) = fromGregorian ry rm (read d) fix ("","",d) = fromGregorian ry rm (read d)
fix ("",m,"") = fromGregorian ry (read m) 1
fix ("",m,d) = fromGregorian ry (read m) (read d) fix ("",m,d) = fromGregorian ry (read m) (read d)
fix (y,"","") = fromGregorian (read y) 1 1
fix (y,m,"") = fromGregorian (read y) (read m) 1
fix (y,m,d) = fromGregorian (read y) (read m) (read d) fix (y,m,d) = fromGregorian (read y) (read m) (read d)
(ry,rm,rd) = toGregorian refdate (ry,rm,rd) = toGregorian refdate
@ -184,13 +221,13 @@ ym = do
datesepchar datesepchar
m <- many1 digit m <- many1 digit
guard (read m <= 12) guard (read m <= 12)
return (y,m,"1") return (y,m,"")
y :: Parser SmartDate y :: Parser SmartDate
y = do y = do
y <- many1 digit y <- many1 digit
guard (read y >= 1000) guard (read y >= 1000)
return (y,"1","1") return (y,"","")
d :: Parser SmartDate d :: Parser SmartDate
d = do d = do
@ -210,19 +247,22 @@ md = do
months = ["january","february","march","april","may","june", months = ["january","february","march","april","may","june",
"july","august","september","october","november","december"] "july","august","september","october","november","december"]
mons = ["jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"] mons = ["jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"]
monthIndex s = maybe 0 (+1) $ (map toLower s) `elemIndex` months
monIndex s = maybe 0 (+1) $ (map toLower s) `elemIndex` mons
month :: Parser SmartDate month :: Parser SmartDate
month = do month = do
m <- choice $ map string months m <- choice $ map (try . string) months
let i = maybe 0 (+1) $ (map toLower m) `elemIndex` months let i = monthIndex m
return ("",show i,"1") return $ ("",show i,"")
mon :: Parser SmartDate mon :: Parser SmartDate
mon = do mon = do
m <- choice $ map string mons m <- choice $ map (try . string) mons
let i = maybe 0 (+1) $ (map toLower m) `elemIndex` mons let i = monIndex m
return ("",show i,"1") return ("",show i,"")
today',yesterday,tomorrow :: Parser SmartDate today',yesterday,tomorrow :: Parser SmartDate
today' = string "today" >> return ("","","today") today' = string "today" >> return ("","","today")

View File

@ -14,7 +14,7 @@ import qualified Data.Map as Map
type SmartDate = (String,String,String) type SmartDate = (String,String,String)
data DateSpan = DateSpan (Maybe Day) (Maybe Day) data DateSpan = DateSpan (Maybe Day) (Maybe Day) deriving (Eq,Show,Ord)
type AccountName = String type AccountName = String

View File

@ -10,6 +10,8 @@ import Ledger.Types
import Ledger.Dates import Ledger.Dates
versionno = "0.3pre"
version = printf "hledger version %s \n" versionno :: String
defaultfile = "~/.ledger" defaultfile = "~/.ledger"
fileenvvar = "LEDGER" fileenvvar = "LEDGER"
usagehdr = "Usage: hledger [OPTS] COMMAND [ACCTPATTERNS] [-- DESCPATTERNS]\n" ++ usagehdr = "Usage: hledger [OPTS] COMMAND [ACCTPATTERNS] [-- DESCPATTERNS]\n" ++
@ -37,6 +39,7 @@ options = [
Option ['f'] ["file"] (ReqArg File "FILE") filehelp, Option ['f'] ["file"] (ReqArg File "FILE") filehelp,
Option ['b'] ["begin"] (ReqArg Begin "DATE") "report on entries on or after this date", Option ['b'] ["begin"] (ReqArg Begin "DATE") "report on entries on or after this date",
Option ['e'] ["end"] (ReqArg End "DATE") "report on entries prior to this date", Option ['e'] ["end"] (ReqArg End "DATE") "report on entries prior to this date",
Option ['p'] ["period"] (ReqArg Period "EXPR") "report on entries during this calendar period",
Option ['C'] ["cleared"] (NoArg Cleared) "report only on cleared entries", Option ['C'] ["cleared"] (NoArg Cleared) "report only on cleared entries",
Option ['B'] ["cost","basis"] (NoArg CostBasis) "report cost basis of commodities", Option ['B'] ["cost","basis"] (NoArg CostBasis) "report cost basis of commodities",
Option [] ["depth"] (ReqArg Depth "N") "balance report: maximum account depth to show", Option [] ["depth"] (ReqArg Depth "N") "balance report: maximum account depth to show",
@ -57,13 +60,14 @@ options = [
-- | An option value from a command-line flag. -- | An option value from a command-line flag.
data Opt = data Opt =
File String | File {value::String} |
Begin String | Begin {value::String} |
End String | End {value::String} |
Period {value::String} |
Cleared | Cleared |
CostBasis | CostBasis |
Depth String | Depth {value::String} |
Display String | Display {value::String} |
Empty | Empty |
Real | Real |
OptionsAnywhere | OptionsAnywhere |
@ -74,8 +78,9 @@ data Opt =
Version Version
deriving (Show,Eq) deriving (Show,Eq)
versionno = "0.3pre" -- yow..
version = printf "hledger version %s \n" versionno :: String optValuesForConstructor f opts = concatMap get opts
where get o = if f v == o then [v] else [] where v = value o
-- | 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. Also any dates in the options are -- name, and ledger command arguments. Also any dates in the options are
@ -104,28 +109,24 @@ fixOptDates opts = do
where fixbracketeddatestr s = "[" ++ (fixSmartDateStr t $ init $ tail s) ++ "]" where fixbracketeddatestr s = "[" ++ (fixSmartDateStr t $ init $ tail s) ++ "]"
fixopt _ o = o fixopt _ o = o
-- | Get the ledger file path from options, an environment variable, or a default -- | Figure out the date span we should report on, based on any
ledgerFilePathFromOpts :: [Opt] -> IO String -- begin/end/period options provided. This could be really smart but I'm
ledgerFilePathFromOpts opts = do -- just going to look for 1. the first Period or 2. the first Begin and
envordefault <- getEnv fileenvvar `catch` \_ -> return defaultfile -- first End.
paths <- mapM tildeExpand $ [envordefault] ++ (concatMap getfile opts) dateSpanFromOpts :: Day -> [Opt] -> DateSpan
return $ last paths dateSpanFromOpts refdate opts
| not $ null ps = spanFromPeriodExpr refdate $ head ps
| otherwise = DateSpan firstb firste
where where
getfile (File s) = [s] ps = optValuesForConstructor Period opts
getfile _ = [] firstb = listtomaybeday $ optValuesForConstructor Begin opts
firste = listtomaybeday $ optValuesForConstructor End opts
listtomaybeday [] = Nothing
listtomaybeday vs = Just $ parse $ head vs
parse s = parsedate $ printf "%04s/%02s/%02s" y m d
where (y,m,d) = fromparse $ parsewith smartdate $ s
-- | Expand ~ in a file path (does not handle ~name). spanFromPeriodExpr refdate = spanFromSmartDateString refdate
tildeExpand :: FilePath -> IO FilePath
tildeExpand ('~':[]) = getHomeDirectory
tildeExpand ('~':'/':xs) = getHomeDirectory >>= return . (++ ('/':xs))
--handle ~name, requires -fvia-C or ghc 6.8:
--import System.Posix.User
-- tildeExpand ('~':xs) = do let (user, path) = span (/= '/') xs
-- pw <- getUserEntryForName user
-- return (homeDirectory pw ++ path)
tildeExpand xs = return xs
dateSpanFromOpts opts = DateSpan (beginDateFromOpts opts) (endDateFromOpts opts)
-- | Get the value of the begin date option, if any. -- | Get the value of the begin date option, if any.
beginDateFromOpts :: [Opt] -> Maybe Day beginDateFromOpts :: [Opt] -> Maybe Day
@ -153,6 +154,17 @@ endDateFromOpts opts =
defaultdate = "" defaultdate = ""
(y,m,d) = fromparse $ parsewith smartdate $ last endopts (y,m,d) = fromparse $ parsewith smartdate $ last endopts
-- | Get the value of the period option, if any.
periodFromOpts :: [Opt] -> Maybe String
periodFromOpts opts =
if null periodopts
then Nothing
else Just $ head periodopts
where
periodopts = concatMap getperiod opts
getperiod (Period s) = [s]
getperiod _ = []
-- | 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
depthFromOpts opts = depthFromOpts opts =
@ -175,6 +187,27 @@ displayFromOpts opts =
getdisplay (Display s) = [s] getdisplay (Display s) = [s]
getdisplay _ = [] getdisplay _ = []
-- | Get the ledger file path from options, an environment variable, or a default
ledgerFilePathFromOpts :: [Opt] -> IO String
ledgerFilePathFromOpts opts = do
envordefault <- getEnv fileenvvar `catch` \_ -> return defaultfile
paths <- mapM tildeExpand $ [envordefault] ++ (concatMap getfile opts)
return $ last paths
where
getfile (File s) = [s]
getfile _ = []
-- | Expand ~ in a file path (does not handle ~name).
tildeExpand :: FilePath -> IO FilePath
tildeExpand ('~':[]) = getHomeDirectory
tildeExpand ('~':'/':xs) = getHomeDirectory >>= return . (++ ('/':xs))
--handle ~name, requires -fvia-C or ghc 6.8:
--import System.Posix.User
-- tildeExpand ('~':xs) = do let (user, path) = span (/= '/') xs
-- pw <- getUserEntryForName user
-- return (homeDirectory pw ++ path)
tildeExpand xs = return xs
-- | Gather any ledger-style account/description pattern arguments into -- | Gather any ledger-style account/description pattern arguments into
-- two lists. These are 0 or more account patterns optionally followed by -- two lists. These are 0 or more account patterns optionally followed by
-- a separator and then 0 or more description patterns. The separator is -- a separator and then 0 or more description patterns. The separator is

View File

@ -346,18 +346,30 @@ registercommand_tests = TestList [
, ,
"register report with display expression" ~: "register report with display expression" ~:
do do
"d<[2008/6/2]" `displayexprgivestxns` ["2008/01/01","2008/06/01"] "d<[2008/6/2]" `displayexprgives` ["2008/01/01","2008/06/01"]
"d<=[2008/6/2]" `displayexprgivestxns` ["2008/01/01","2008/06/01","2008/06/02"] "d<=[2008/6/2]" `displayexprgives` ["2008/01/01","2008/06/01","2008/06/02"]
"d=[2008/6/2]" `displayexprgivestxns` ["2008/06/02"] "d=[2008/6/2]" `displayexprgives` ["2008/06/02"]
"d>=[2008/6/2]" `displayexprgivestxns` ["2008/06/02","2008/06/03","2008/12/31"] "d>=[2008/6/2]" `displayexprgives` ["2008/06/02","2008/06/03","2008/12/31"]
"d>[2008/6/2]" `displayexprgivestxns` ["2008/06/03","2008/12/31"] "d>[2008/6/2]" `displayexprgives` ["2008/06/03","2008/12/31"]
] ,
"register report with period expression" ~:
do
"" `periodexprgives` ["2008/01/01","2008/06/01","2008/06/02","2008/06/03","2008/12/31"]
"2008" `periodexprgives` ["2008/01/01","2008/06/01","2008/06/02","2008/06/03","2008/12/31"]
-- need to get datespan into ledgerFromString, or preconvert period expressions
-- "2007" `periodexprgives` []
]
where where
expr `displayexprgivestxns` dates = expr `displayexprgives` dates =
assertequal dates (datesfromregister r) assertequal dates (datesfromregister r)
where where
r = showRegisterReport [Display expr] [] l r = showRegisterReport [Display expr] [] l
l = ledgerfromstring [] sample_ledger_str l = ledgerfromstring [] sample_ledger_str
expr `periodexprgives` dates =
assertequal dates (datesfromregister r)
where
r = showRegisterReport [Period expr] [] l
l = ledgerfromstring [] sample_ledger_str
datesfromregister = filter (not . null) . map (strip . take 10) . lines datesfromregister = filter (not . null) . map (strip . take 10) . lines

View File

@ -69,11 +69,12 @@ main = do
-- (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
day <- today
let span = dateSpanFromOpts day opts
let runcmd = cmd opts args . cacheLedger apats . filterRawLedger span 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 span dpats c r . canonicaliseAmounts costbasis
(apats,dpats) = parseAccountDescriptionArgs opts args (apats,dpats) = parseAccountDescriptionArgs opts args
span = dateSpanFromOpts 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