imp: timedot: make one multi-posting transaction per date line (#1754)

Descriptions and comments are now more straightforward and similar to
journal format.
This commit is contained in:
Simon Michael 2023-05-02 19:08:03 -10:00
parent 50349f81f7
commit bbecd611f1
4 changed files with 105 additions and 135 deletions

View File

@ -1,22 +1,16 @@
2016/2/1 # sample.timedot
fos:haskell .... # This is a comment line
biz:research . ; Also a comment line
inc:client1 .... .... .... .... .... .... * Org headings before the first date are also comment lines
2016/2/2 2023-01-01 transaction description
biz:research . biz:research ....
inc:client1 .... .... .. inc:client1 .... ..
2016/2/3 2023-01-01 different transaction, same day ; with a comment and transaction-tag:
biz:research . ; more transaction comment lines ? currently ignored
fos:hledger .... .... ... fos:haskell .... ; a posting comment and posting-tag:
biz:it .... .. ; more posting comment lines ? currently ignored
inc:client1 .... .... .... .... .... per:admin ....
2016/2/4
biz:research .... ..
fos:hledger .... .... ....
fos:ledger 0.25
fos:haskell .5
inc:client1 2
** 2023-01-02 ; dates are allowed to be org headings

View File

@ -43,7 +43,6 @@ import Control.Monad
import Control.Monad.Except (ExceptT, liftEither) import Control.Monad.Except (ExceptT, liftEither)
import Control.Monad.State.Strict import Control.Monad.State.Strict
import Data.Char (isSpace) import Data.Char (isSpace)
import Data.List (foldl')
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as T import qualified Data.Text as T
import Data.Time (Day) import Data.Time (Day)
@ -113,7 +112,7 @@ preamblep = do
many $ notFollowedBy datelinep >> (lift $ emptyorcommentlinep "#;*") many $ notFollowedBy datelinep >> (lift $ emptyorcommentlinep "#;*")
lift $ traceparse' "preamblep" 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 -- 2020/2/1 optional day description
-- fos.haskell .... .. -- fos.haskell .... ..
@ -123,14 +122,22 @@ preamblep = do
dayp :: JournalParser m () dayp :: JournalParser m ()
dayp = label "timedot day entry" $ do dayp = label "timedot day entry" $ do
lift $ traceparse "dayp" lift $ traceparse "dayp"
pos <- getSourcePos
(date,desc,comment,tags) <- datelinep (date,desc,comment,tags) <- datelinep
commentlinesp commentlinesp
ts <- many $ entryp <* commentlinesp ps <- many $ timedotentryp <* commentlinesp
modify' $ addTransactions $ map (\t -> t{tdate=date, tdescription=desc, tcomment=comment, ttags=tags}) ts endpos <- getSourcePos
lift $ traceparse' "dayp" -- lift $ traceparse' "dayp end"
where let t = txnTieKnot $ nulltransaction{
addTransactions :: [Transaction] -> Journal -> Journal tsourcepos = (pos, endpos),
addTransactions ts j = foldl' (flip ($)) j (map addTransaction ts) tdate = date,
tstatus = Cleared,
tdescription = desc,
tcomment = comment,
ttags = tags,
tpostings = ps
}
modify' $ addTransaction t
datelinep :: JournalParser m (Day,Text,Text,[Tag]) datelinep :: JournalParser m (Day,Text,Text,[Tag])
datelinep = do datelinep = do
@ -139,7 +146,7 @@ datelinep = do
date <- datep date <- datep
desc <- T.strip <$> lift descriptionp desc <- T.strip <$> lift descriptionp
(comment, tags) <- lift transactioncommentp (comment, tags) <- lift transactioncommentp
lift $ traceparse' "datelinep" -- lift $ traceparse' "datelinep end"
return (date, desc, comment, tags) return (date, desc, comment, tags)
-- | Zero or more empty lines or hash/semicolon comment lines -- | Zero or more empty lines or hash/semicolon comment lines
@ -165,10 +172,9 @@ orgheadingprefixp = do
-- @ -- @
-- fos.haskell .... .. -- fos.haskell .... ..
-- @ -- @
entryp :: JournalParser m Transaction timedotentryp :: JournalParser m Posting
entryp = do timedotentryp = do
lift $ traceparse "entryp" lift $ traceparse "timedotentryp"
pos <- getSourcePos
notFollowedBy datelinep notFollowedBy datelinep
lift $ optional $ choice [orgheadingprefixp, skipNonNewlineSpaces1] lift $ optional $ choice [orgheadingprefixp, skipNonNewlineSpaces1]
a <- modifiedaccountnamep a <- modifiedaccountnamep
@ -188,21 +194,13 @@ entryp = do
(c,s) = case mcs of (c,s) = case mcs of
Just (defc,defs) -> (defc, defs{asprecision=max (asprecision defs) (Precision 2)}) Just (defc,defs) -> (defc, defs{asprecision=max (asprecision defs) (Precision 2)})
_ -> ("", amountstyle{asprecision=Precision 2}) _ -> ("", amountstyle{asprecision=Precision 2})
t = nulltransaction{ -- lift $ traceparse' "timedotentryp end"
tsourcepos = (pos, pos), return $ nullposting{paccount=a
tstatus = Cleared,
tpostings = [
nullposting{paccount=a
,pamount=mixedAmount $ nullamt{acommodity=c, aquantity=hours, astyle=s} ,pamount=mixedAmount $ nullamt{acommodity=c, aquantity=hours, astyle=s}
,ptype=VirtualPosting ,ptype=VirtualPosting
,pcomment=comment ,pcomment=comment
,ptags=tags ,ptags=tags
,ptransaction=Just t
} }
]
}
lift $ traceparse' "entryp"
return t
type Hours = Quantity type Hours = Quantity

View File

@ -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: A timedot file contains a series of day entries, which might look like this:
```timedot ```timedot
2021-08-04 2023-05-01
hom:errands .... .... hom:errands .... .... ; two hours
fos:hledger:timedot .. ; docs fos:hledger:timedot .. ; half an hour
per:admin:finance per:admin:finance
``` ```
hledger reads this as three time transactions on this day, hledger reads this as a transaction on this day with three (unbalanced) postings,
with each dot representing a quarter-hour spent: 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 ```shell
$ hledger -f a.timedot print # .timedot file extension activates the timedot reader $ hledger -f a.timedot print # .timedot file extension (or timedot: prefix) is required
2021-08-04 * 2023-05-01 *
(hom:errands) 2.00 (hom:errands) 2.00 ; two hours
(fos:hledger:timedot) 0.50 ; half an hour
2021-08-04 *
(fos:hledger:timedot) 0.50 ; docs
2021-08-04 *
(per:admin:finance) 0 (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 - an **account name** - any hledger-style [account name](#account-names), optionally hierarchical, optionally indented.
- 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).
- **two or more spaces** - a field separator, required if there is an amount (as in journal format). - **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 optional **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 **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. Spaces are ignored and can be used for grouping.
Eg: `.... ..` Eg: `.... ..`
- a **number**, representing hours. Eg: `1.5` - or a **number**. Eg: `1.5`
- a **number immediately followed by a unit symbol** - or a **number immediately followed by a unit symbol**
`s`, `m`, `h`, `d`, `w`, `mo`, or `y`, `s`, `m`, `h`, `d`, `w`, `mo`, or `y`.
representing seconds, minutes, hours, days weeks, months or years. These are interpreted as seconds, minutes, hours, days weeks, months or years, and converted to hours, assuming:\
Eg `1.5h` or `90m`.
The following equivalencies are assumed:\
`60s` = `1m`, `60s` = `1m`,
`60m` = `1h`, `60m` = `1h`,
`24h` = `1d`, `24h` = `1d`,
`7d` = `1w`, `7d` = `1w`,
`30d` = `1mo`, `30d` = `1mo`,
`365d` = `1y`. `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 There is some added flexibility to help with keeping time log data
in the same file as your notes, todo lists, etc.: 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. - Blank lines and lines beginning with `#` or `;` are ignored.
- Before the first date line, lines beginning with `*` 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. 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, This means the time log can be kept under an Org headline,
and date lines or time transaction lines can be Org headlines. and date lines or time transaction lines can be Org headlines.
- Lines not ending with a double-space and amount are - Lines not ending with a double-space and amount are parsed as postings with zero amount.
parsed as transactions with zero amount. Note hledger's register reports hide these by default (add -E to see them).
(Most hledger reports hide these by default; add -E to see them.)
More examples: More examples:

View File

@ -1,58 +1,44 @@
# 1. basic timedot entry # timedot format
< <
# file comment # sample.timedot
; another file comment # This is a comment line
; Also a comment line
* Org headings before the first date are also comment lines
2020-01-01 2023-01-01 transaction description
a:aa 1 biz:research ....
b:bb 2 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 $ hledger -ftimedot:- print
2020-01-01 * 2023-01-01 * transaction description
(a:aa) 1.00 (biz:research) 1.00
(inc:client1) 1.50
2020-01-01 * 2023-01-01 * different transaction, same day ; with a comment and transaction-tag:
(b:bb) 2.00 (fos:haskell) 1.00 ; a posting comment and posting-tag:
(per:admin) 1.00
>=0 2023-01-02 * ; dates are allowed to be org headings
# 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:
>= >=
# 5. Transaction descriptions, comments and tags are parsed properly. # 2. And this register.
$ hledger -ftimedot:- descriptions tag:day-tag $ hledger -ftimedot:- reg
day description 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. # 3. Tags are recognised. Account aliases are applied.
$ hledger -ftimedot:- reg tag:posting-tag $ hledger -ftimedot:- reg tag:posting-tag --alias fos:haskell=λ
2023-01-01 day description (b) 1.00 1.00 2023-01-01 different transac.. (λ) 1.00 1.00