2010-05-30 23:11:58 +04:00
|
|
|
{-|
|
|
|
|
|
2016-04-13 07:10:02 +03:00
|
|
|
A reader for the timeclock file format generated by timeclock.el
|
2012-03-24 22:08:11 +04:00
|
|
|
(<http://www.emacswiki.org/emacs/TimeClock>). Example:
|
|
|
|
|
|
|
|
@
|
|
|
|
i 2007\/03\/10 12:26:00 hledger
|
|
|
|
o 2007\/03\/10 17:26:02
|
|
|
|
@
|
2010-05-30 23:11:58 +04:00
|
|
|
|
|
|
|
From timeclock.el 2.6:
|
|
|
|
|
|
|
|
@
|
2016-04-13 07:10:02 +03:00
|
|
|
A timeclock contains data in the form of a single entry per line.
|
2010-05-30 23:11:58 +04:00
|
|
|
Each entry has the form:
|
|
|
|
|
|
|
|
CODE YYYY/MM/DD HH:MM:SS [COMMENT]
|
|
|
|
|
|
|
|
CODE is one of: b, h, i, o or O. COMMENT is optional when the code is
|
|
|
|
i, o or O. The meanings of the codes are:
|
|
|
|
|
|
|
|
b Set the current time balance, or \"time debt\". Useful when
|
|
|
|
archiving old log data, when a debt must be carried forward.
|
|
|
|
The COMMENT here is the number of seconds of debt.
|
|
|
|
|
|
|
|
h Set the required working time for the given day. This must
|
|
|
|
be the first entry for that day. The COMMENT in this case is
|
|
|
|
the number of hours in this workday. Floating point amounts
|
|
|
|
are allowed.
|
|
|
|
|
|
|
|
i Clock in. The COMMENT in this case should be the name of the
|
|
|
|
project worked on.
|
|
|
|
|
|
|
|
o Clock out. COMMENT is unnecessary, but can be used to provide
|
|
|
|
a description of how the period went, for example.
|
|
|
|
|
|
|
|
O Final clock out. Whatever project was being worked on, it is
|
|
|
|
now finished. Useful for creating summary reports.
|
|
|
|
@
|
|
|
|
|
|
|
|
-}
|
|
|
|
|
2016-04-13 07:10:02 +03:00
|
|
|
module Hledger.Read.TimeclockReader (
|
2012-03-24 22:08:11 +04:00
|
|
|
-- * Reader
|
|
|
|
reader,
|
2016-05-18 05:46:54 +03:00
|
|
|
-- * Misc other exports
|
|
|
|
timeclockfilep,
|
2012-03-24 22:08:11 +04:00
|
|
|
-- * Tests
|
2016-04-13 07:10:02 +03:00
|
|
|
tests_Hledger_Read_TimeclockReader
|
2010-05-31 05:15:18 +04:00
|
|
|
)
|
2010-05-30 23:11:58 +04:00
|
|
|
where
|
2015-04-29 17:10:13 +03:00
|
|
|
import Prelude ()
|
|
|
|
import Prelude.Compat
|
|
|
|
import Control.Monad (liftM)
|
|
|
|
import Control.Monad.Except (ExceptT)
|
2016-02-20 10:02:10 +03:00
|
|
|
import Data.List (foldl')
|
2015-04-28 23:54:36 +03:00
|
|
|
import Data.Maybe (fromMaybe)
|
2011-05-28 08:11:44 +04:00
|
|
|
import Test.HUnit
|
2014-11-03 08:52:12 +03:00
|
|
|
import Text.Parsec hiding (parse)
|
2012-03-24 22:08:11 +04:00
|
|
|
import System.FilePath
|
2011-05-28 08:11:44 +04:00
|
|
|
|
2010-05-30 23:11:58 +04:00
|
|
|
import Hledger.Data
|
2012-05-09 19:34:05 +04:00
|
|
|
-- XXX too much reuse ?
|
2016-05-18 05:46:54 +03:00
|
|
|
import Hledger.Read.Common (
|
|
|
|
emptyorcommentlinep, datetimep, parseAndFinaliseJournal, modifiedaccountnamep, genericSourcePos
|
2012-03-24 22:08:11 +04:00
|
|
|
)
|
2011-05-28 08:11:44 +04:00
|
|
|
import Hledger.Utils
|
2010-05-30 23:11:58 +04:00
|
|
|
|
|
|
|
|
2010-06-25 18:56:48 +04:00
|
|
|
reader :: Reader
|
|
|
|
reader = Reader format detect parse
|
|
|
|
|
|
|
|
format :: String
|
2016-04-13 07:10:02 +03:00
|
|
|
format = "timeclock"
|
2010-06-25 18:56:48 +04:00
|
|
|
|
2016-04-13 07:10:02 +03:00
|
|
|
-- | Does the given file path and data look like it might be timeclock.el's timeclock format ?
|
2010-06-25 18:56:48 +04:00
|
|
|
detect :: FilePath -> String -> Bool
|
2014-05-10 04:55:32 +04:00
|
|
|
detect f s
|
2016-02-20 10:02:10 +03:00
|
|
|
| f /= "-" = takeExtension f == '.':format -- from a known file name: yes if the extension is this format's name
|
|
|
|
| otherwise = regexMatches "(^|\n)[io] " s -- from stdin: yes if any line starts with "i " or "o "
|
2010-06-25 18:56:48 +04:00
|
|
|
|
2016-04-13 07:10:02 +03:00
|
|
|
-- | Parse and post-process a "Journal" from timeclock.el's timeclock
|
2010-05-30 23:11:58 +04:00
|
|
|
-- format, saving the provided file path and the current time, or give an
|
|
|
|
-- error.
|
2015-03-29 17:53:23 +03:00
|
|
|
parse :: Maybe FilePath -> Bool -> FilePath -> String -> ExceptT String IO Journal
|
2016-04-13 07:10:02 +03:00
|
|
|
parse _ = parseAndFinaliseJournal timeclockfilep
|
2010-05-30 23:11:58 +04:00
|
|
|
|
2016-05-19 06:57:34 +03:00
|
|
|
timeclockfilep :: ParsecT [Char] JournalParseState (ExceptT String IO) (JournalUpdate, JournalParseState)
|
2016-04-13 07:10:02 +03:00
|
|
|
timeclockfilep = do items <- many timeclockitemp
|
|
|
|
eof
|
2016-05-19 06:57:34 +03:00
|
|
|
jps <- getState
|
|
|
|
return (liftM (foldl' (\acc new x -> new (acc x)) id) $ sequence items, jps)
|
2014-09-11 00:07:53 +04:00
|
|
|
where
|
2010-05-30 23:11:58 +04:00
|
|
|
-- As all ledger line types can be distinguished by the first
|
|
|
|
-- character, excepting transactions versus empty (blank or
|
|
|
|
-- comment-only) lines, can use choice w/o try
|
2016-05-18 05:46:54 +03:00
|
|
|
timeclockitemp = choice [
|
|
|
|
emptyorcommentlinep >> return (return id)
|
2016-04-13 07:10:02 +03:00
|
|
|
, liftM (return . addTimeclockEntry) timeclockentryp
|
|
|
|
] <?> "timeclock entry, or default year or historical price directive"
|
2010-05-30 23:11:58 +04:00
|
|
|
|
2016-04-13 07:10:02 +03:00
|
|
|
-- | Parse a timeclock entry.
|
2016-05-19 06:57:34 +03:00
|
|
|
timeclockentryp :: ParsecT [Char] JournalParseState (ExceptT String IO) TimeclockEntry
|
2016-04-13 07:10:02 +03:00
|
|
|
timeclockentryp = do
|
2015-06-29 02:20:28 +03:00
|
|
|
sourcepos <- genericSourcePos <$> getPosition
|
2010-05-30 23:11:58 +04:00
|
|
|
code <- oneOf "bhioO"
|
|
|
|
many1 spacenonewline
|
2014-02-06 07:30:01 +04:00
|
|
|
datetime <- datetimep
|
2015-09-25 03:23:52 +03:00
|
|
|
account <- fromMaybe "" <$> optionMaybe (many1 spacenonewline >> modifiedaccountnamep)
|
2015-04-28 23:54:36 +03:00
|
|
|
description <- fromMaybe "" <$> optionMaybe (many1 spacenonewline >> restofline)
|
2016-04-13 07:10:02 +03:00
|
|
|
return $ TimeclockEntry sourcepos (read [code]) datetime account description
|
2010-05-30 23:11:58 +04:00
|
|
|
|
2016-04-13 07:10:02 +03:00
|
|
|
tests_Hledger_Read_TimeclockReader = TestList [
|
2010-05-30 23:11:58 +04:00
|
|
|
]
|
|
|
|
|