From bbecd611f1cb104666acaff2fa11702b7f00d001 Mon Sep 17 00:00:00 2001 From: Simon Michael Date: Tue, 2 May 2023 19:08:03 -1000 Subject: [PATCH] imp: timedot: make one multi-posting transaction per date line (#1754) Descriptions and comments are now more straightforward and similar to journal format. --- examples/sample.timedot | 32 ++++----- hledger-lib/Hledger/Read/TimedotReader.hs | 44 ++++++------ hledger/hledger.m4.md | 82 ++++++++++------------- hledger/test/timedot.test | 82 ++++++++++------------- 4 files changed, 105 insertions(+), 135 deletions(-) diff --git a/examples/sample.timedot b/examples/sample.timedot index 306fa1823..85c4913ed 100644 --- a/examples/sample.timedot +++ b/examples/sample.timedot @@ -1,22 +1,16 @@ -2016/2/1 -fos:haskell .... -biz:research . -inc:client1 .... .... .... .... .... .... +# sample.timedot +# This is a comment line +; Also a comment line +* Org headings before the first date are also comment lines -2016/2/2 -biz:research . -inc:client1 .... .... .. +2023-01-01 transaction description +biz:research .... +inc:client1 .... .. -2016/2/3 -biz:research . -fos:hledger .... .... ... -biz:it .... .. -inc:client1 .... .... .... .... .... - -2016/2/4 -biz:research .... .. -fos:hledger .... .... .... -fos:ledger 0.25 -fos:haskell .5 -inc:client1 2 +2023-01-01 different transaction, same day ; with a comment and transaction-tag: +; more transaction comment lines ? currently ignored +fos:haskell .... ; a posting comment and posting-tag: +; more posting comment lines ? currently ignored +per:admin .... +** 2023-01-02 ; dates are allowed to be org headings diff --git a/hledger-lib/Hledger/Read/TimedotReader.hs b/hledger-lib/Hledger/Read/TimedotReader.hs index 2a28019df..ba78f67cb 100644 --- a/hledger-lib/Hledger/Read/TimedotReader.hs +++ b/hledger-lib/Hledger/Read/TimedotReader.hs @@ -43,7 +43,6 @@ import Control.Monad import Control.Monad.Except (ExceptT, liftEither) import Control.Monad.State.Strict import Data.Char (isSpace) -import Data.List (foldl') import Data.Text (Text) import qualified Data.Text as T import Data.Time (Day) @@ -113,7 +112,7 @@ preamblep = do many $ notFollowedBy datelinep >> (lift $ emptyorcommentlinep "#;*") lift $ traceparse' "preamblep" --- | Parse timedot day entries to zero or more time transactions for that day. +-- | Parse timedot day entries to multi-posting time transactions for that day. -- @ -- 2020/2/1 optional day description -- fos.haskell .... .. @@ -123,14 +122,22 @@ preamblep = do dayp :: JournalParser m () dayp = label "timedot day entry" $ do lift $ traceparse "dayp" + pos <- getSourcePos (date,desc,comment,tags) <- datelinep commentlinesp - ts <- many $ entryp <* commentlinesp - modify' $ addTransactions $ map (\t -> t{tdate=date, tdescription=desc, tcomment=comment, ttags=tags}) ts - lift $ traceparse' "dayp" - where - addTransactions :: [Transaction] -> Journal -> Journal - addTransactions ts j = foldl' (flip ($)) j (map addTransaction ts) + ps <- many $ timedotentryp <* commentlinesp + endpos <- getSourcePos + -- lift $ traceparse' "dayp end" + let t = txnTieKnot $ nulltransaction{ + tsourcepos = (pos, endpos), + tdate = date, + tstatus = Cleared, + tdescription = desc, + tcomment = comment, + ttags = tags, + tpostings = ps + } + modify' $ addTransaction t datelinep :: JournalParser m (Day,Text,Text,[Tag]) datelinep = do @@ -139,7 +146,7 @@ datelinep = do date <- datep desc <- T.strip <$> lift descriptionp (comment, tags) <- lift transactioncommentp - lift $ traceparse' "datelinep" + -- lift $ traceparse' "datelinep end" return (date, desc, comment, tags) -- | Zero or more empty lines or hash/semicolon comment lines @@ -165,10 +172,9 @@ orgheadingprefixp = do -- @ -- fos.haskell .... .. -- @ -entryp :: JournalParser m Transaction -entryp = do - lift $ traceparse "entryp" - pos <- getSourcePos +timedotentryp :: JournalParser m Posting +timedotentryp = do + lift $ traceparse "timedotentryp" notFollowedBy datelinep lift $ optional $ choice [orgheadingprefixp, skipNonNewlineSpaces1] a <- modifiedaccountnamep @@ -188,21 +194,13 @@ entryp = do (c,s) = case mcs of Just (defc,defs) -> (defc, defs{asprecision=max (asprecision defs) (Precision 2)}) _ -> ("", amountstyle{asprecision=Precision 2}) - t = nulltransaction{ - tsourcepos = (pos, pos), - tstatus = Cleared, - tpostings = [ - nullposting{paccount=a + -- lift $ traceparse' "timedotentryp end" + return $ nullposting{paccount=a ,pamount=mixedAmount $ nullamt{acommodity=c, aquantity=hours, astyle=s} ,ptype=VirtualPosting ,pcomment=comment ,ptags=tags - ,ptransaction=Just t } - ] - } - lift $ traceparse' "entryp" - return t type Hours = Quantity diff --git a/hledger/hledger.m4.md b/hledger/hledger.m4.md index d3f44643f..241be574b 100644 --- a/hledger/hledger.m4.md +++ b/hledger/hledger.m4.md @@ -4174,63 +4174,55 @@ Compared to [`timeclock` format](#timeclock), it is A timedot file contains a series of day entries, which might look like this: ```timedot -2021-08-04 -hom:errands .... .... -fos:hledger:timedot .. ; docs -per:admin:finance +2023-05-01 +hom:errands .... .... ; two hours +fos:hledger:timedot .. ; half an hour +per:admin:finance ``` -hledger reads this as three time transactions on this day, -with each dot representing a quarter-hour spent: +hledger reads this as a transaction on this day with three (unbalanced) postings, +where each dot represents "0.25". No commodity is assumed, but normally we interpret +it as hours, with each dot representing a quarter-hour. It's convenient, though +not required, to group the dots in fours for easy reading. ```shell -$ hledger -f a.timedot print # .timedot file extension activates the timedot reader -2021-08-04 * - (hom:errands) 2.00 - -2021-08-04 * - (fos:hledger:timedot) 0.50 ; docs - -2021-08-04 * - (per:admin:finance) 0 +$ hledger -f a.timedot print # .timedot file extension (or timedot: prefix) is required +2023-05-01 * + (hom:errands) 2.00 ; two hours + (fos:hledger:timedot) 0.50 ; half an hour + (per:admin:finance) 0 ``` -A day entry begins with a date line: +A transaction begins with a non-indented **[simple date](#simple-dates)** (Y-M-D, Y/M/D, or Y.M.D). +It can optionally be preceded by one or more stars and a space, for Emacs org mode compatibility. +It can optionally be followed on the same line by a transaction description, +and/or a transaction comment following a semicolon. -- a non-indented **[simple date](#simple-dates)** (Y-M-D, Y/M/D, or Y.M.D). +After the date line are zero or more time postings, consisting of: -Optionally this can be followed on the same line by - -- a **common description** for this day's transactions. -- a **common comment** for this day's transactions, following a semicolon (`;`). - -After the date line are zero or more optionally-indented time transactions, consisting of: - -- an **account name** - any word or phrase, usually a hledger-style [account name](#account-names). +- an **account name** - any hledger-style [account name](#account-names), optionally hierarchical, optionally indented. - **two or more spaces** - a field separator, required if there is an amount (as in journal format). -- a **timedot amount** - dots representing quarter hours, or a number representing hours, optionally with a unit suffix. -- an **posting comment** for this transaction, following with semicolon. +- an optional **timedot amount** - dots representing quarter hours, or a number representing hours, optionally with a unit suffix. +- an optional **posting comment** following a semicolon. -In more detail, timedot amounts can be: +Timedot amounts can be: -- **dots**: zero or more period characters, each representing one quarter-hour. +- **dots**: zero or more period characters (`.`), each representing 0.25. Spaces are ignored and can be used for grouping. Eg: `.... ..` -- a **number**, representing hours. Eg: `1.5` +- or a **number**. Eg: `1.5` -- a **number immediately followed by a unit symbol** - `s`, `m`, `h`, `d`, `w`, `mo`, or `y`, - representing seconds, minutes, hours, days weeks, months or years. - Eg `1.5h` or `90m`. - The following equivalencies are assumed:\ - `60s` = `1m`, - `60m` = `1h`, - `24h` = `1d`, - `7d` = `1w`, - `30d` = `1mo`, +- or a **number immediately followed by a unit symbol** + `s`, `m`, `h`, `d`, `w`, `mo`, or `y`. + These are interpreted as seconds, minutes, hours, days weeks, months or years, and converted to hours, assuming:\ + `60s` = `1m`, + `60m` = `1h`, + `24h` = `1d`, + `7d` = `1w`, + `30d` = `1mo`, `365d` = `1y`. - (This unit will not be visible in the generated transaction amount, which is always in hours.) + Eg `90m` is parsed as `1.5`. There is some added flexibility to help with keeping time log data in the same file as your notes, todo lists, etc.: @@ -4238,14 +4230,14 @@ in the same file as your notes, todo lists, etc.: - Blank lines and lines beginning with `#` or `;` are ignored. - Before the first date line, lines beginning with `*` are ignored. - From the first date line onward, a sequence of `*`'s followed by a space + +- From the first date line onward, one or more `*`'s followed by a space at beginning of lines (ie, the headline prefix used by Emacs Org mode) is ignored. This means the time log can be kept under an Org headline, and date lines or time transaction lines can be Org headlines. -- Lines not ending with a double-space and amount are - parsed as transactions with zero amount. - (Most hledger reports hide these by default; add -E to see them.) +- Lines not ending with a double-space and amount are parsed as postings with zero amount. + Note hledger's register reports hide these by default (add -E to see them). More examples: diff --git a/hledger/test/timedot.test b/hledger/test/timedot.test index 622e56c68..1c219a4cc 100644 --- a/hledger/test/timedot.test +++ b/hledger/test/timedot.test @@ -1,58 +1,44 @@ -# 1. basic timedot entry +# timedot format + < -# file comment -; another file comment +# sample.timedot +# This is a comment line +; Also a comment line +* Org headings before the first date are also comment lines -2020-01-01 -a:aa 1 -b:bb 2 +2023-01-01 transaction description +biz:research .... +inc:client1 .... .. +2023-01-01 different transaction, same day ; with a comment and transaction-tag: +; more transaction comment lines ? currently ignored +fos:haskell .... ; a posting comment and posting-tag: +; more posting comment lines ? currently ignored +per:admin .... + +** 2023-01-02 ; dates are allowed to be org headings + +# 1. The above timedot is converted to these transactions. $ hledger -ftimedot:- print -2020-01-01 * - (a:aa) 1.00 +2023-01-01 * transaction description + (biz:research) 1.00 + (inc:client1) 1.50 -2020-01-01 * - (b:bb) 2.00 +2023-01-01 * different transaction, same day ; with a comment and transaction-tag: + (fos:haskell) 1.00 ; a posting comment and posting-tag: + (per:admin) 1.00 ->=0 - -# 2. Org mode headline prefixes are ignored. -< -* 2020-01-01 -** a:aa 1 - -$ hledger -ftimedot:- print -2020-01-01 * - (a:aa) 1.00 - ->=0 - -# 3. Command-line account aliases are applied. -$ hledger -ftimedot:- print --alias a=b -2020-01-01 * - (b:aa) 1.00 - ->=0 - -# 4. A common day description and comment, and posting comments are supported. -< -2023-01-01 day description ; day comment, day-tag: -a .... -b .... ; posting comment, posting-tag: - -$ hledger -ftimedot:- print -2023-01-01 * day description ; day comment, day-tag: - (a) 1.00 - -2023-01-01 * day description ; day comment, day-tag: - (b) 1.00 ; posting comment, posting-tag: +2023-01-02 * ; dates are allowed to be org headings >= -# 5. Transaction descriptions, comments and tags are parsed properly. -$ hledger -ftimedot:- descriptions tag:day-tag -day description +# 2. And this register. +$ hledger -ftimedot:- reg +2023-01-01 transaction descr.. (biz:research) 1.00 1.00 + (inc:client1) 1.50 2.50 +2023-01-01 different transac.. (fos:haskell) 1.00 3.50 + (per:admin) 1.00 4.50 -# 6. Posting comments and tags are parsed properly. -$ hledger -ftimedot:- reg tag:posting-tag -2023-01-01 day description (b) 1.00 1.00 +# 3. Tags are recognised. Account aliases are applied. +$ hledger -ftimedot:- reg tag:posting-tag --alias fos:haskell=λ +2023-01-01 different transac.. (λ) 1.00 1.00