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