1 {-| 2 3 A reader for the timelog file format generated by timeclock.el. 4 5 From timeclock.el 2.6: 6 7 @ 8 A timelog contains data in the form of a single entry per line. 9 Each entry has the form: 10 11 CODE YYYY/MM/DD HH:MM:SS [COMMENT] 12 13 CODE is one of: b, h, i, o or O. COMMENT is optional when the code is 14 i, o or O. The meanings of the codes are: 15 16 b Set the current time balance, or \"time debt\". Useful when 17 archiving old log data, when a debt must be carried forward. 18 The COMMENT here is the number of seconds of debt. 19 20 h Set the required working time for the given day. This must 21 be the first entry for that day. The COMMENT in this case is 22 the number of hours in this workday. Floating point amounts 23 are allowed. 24 25 i Clock in. The COMMENT in this case should be the name of the 26 project worked on. 27 28 o Clock out. COMMENT is unnecessary, but can be used to provide 29 a description of how the period went, for example. 30 31 O Final clock out. Whatever project was being worked on, it is 32 now finished. Useful for creating summary reports. 33 @ 34 35 Example: 36 37 @ 38 i 2007/03/10 12:26:00 hledger 39 o 2007/03/10 17:26:02 40 @ 41 42 -} 43 44 module Hledger.Read.TimelogReader ( 45 reader, 46 tests_Hledger_Read_TimelogReader 47 ) 48 where 49 import Control.Monad 50 import Control.Monad.Error 51 import Test.HUnit 52 import Text.ParserCombinators.Parsec hiding (parse) 53 54 import Hledger.Data 55 import Hledger.Read.Utils 56 import Hledger.Read.JournalReader (ledgerDirective, ledgerHistoricalPrice, 57 ledgerDefaultYear, emptyLine, ledgerdatetime) 58 import Hledger.Utils 59 60 61 reader :: Reader 62 reader = Reader format detect parse 63 64 format :: String 65 format = "timelog" 66 67 -- | Does the given file path and data provide timeclock.el's timelog format ? 68 detect :: FilePath -> String -> Bool 69 detect f _ = fileSuffix f == format 70 71 -- | Parse and post-process a "Journal" from timeclock.el's timelog 72 -- format, saving the provided file path and the current time, or give an 73 -- error. 74 parse :: FilePath -> String -> ErrorT String IO Journal 75 parse = parseJournalWith timelogFile 76 77 timelogFile :: GenParser Char JournalContext (JournalUpdate,JournalContext) 78 timelogFile = do items <- many timelogItem 79 eof 80 ctx <- getState 81 return (liftM (foldr (.) id) $ sequence items, ctx) 82 where 83 -- As all ledger line types can be distinguished by the first 84 -- character, excepting transactions versus empty (blank or 85 -- comment-only) lines, can use choice w/o try 86 timelogItem = choice [ ledgerDirective 87 , liftM (return . addHistoricalPrice) ledgerHistoricalPrice 88 , ledgerDefaultYear 89 , emptyLine >> return (return id) 90 , liftM (return . addTimeLogEntry) timelogentry 91 ] <?> "timelog entry, or default year or historical price directive" 92 93 -- | Parse a timelog entry. 94 timelogentry :: GenParser Char JournalContext TimeLogEntry 95 timelogentry = do 96 code <- oneOf "bhioO" 97 many1 spacenonewline 98 datetime <- ledgerdatetime 99 comment <- optionMaybe (many1 spacenonewline >> liftM2 (++) getParentAccount restofline) 100 return $ TimeLogEntry (read [code]) datetime (maybe "" rstrip comment) 101 102 tests_Hledger_Read_TimelogReader = TestList [ 103 ] 104