Tidrapporter med ledger

Jag har ju en förkärlek till att göra saker dels krångliga, men även att göra dem krångligare med hjälp av Emacs. Nu har jag äntligen hittat ett sätt att blanda in ytterligare ett steg i kedjan, nämligen ledger!

Jag för min tiddagbok för jobbet med hjälp av en textfil som tolkas av programmet ledger, som i sin tur rapporterar på lite olika sätt. Formatet är jätteenkelt, i princip:

i 2019-08-23 08:00:00 Agio:InternTid
o 2019-08-23 12:00:00

Ovan är också en ren lögn – jag är aldrig på jobbet så tidigt! Men ovan är att jag kom in vid 08:00:00 och startade med något internt för jobbet, och gick på lunch vid 12. Jag behöver inte fördefiniera några kunder eller kostnadsställen, utan jag fyller bara på. Formatet är tekniskt sett inte ett rent ledger-format, men ledger har arbetat in stöd för det ändå.

Men, för att göra livet lite lättare skrev jag lite elisp, se nedan. Står jag i en buffer där major-mode är satt till ledger-mode så kallar jag bara på den interaktiva funktionen monotux/check-in och svarar på frågorna, så infogas svaren längst ner i filen. Koden är inte vacker, Men Den Funkar Bra Nog(tm) så vi kör på den!

;; https://emacs.stackexchange.com/a/7150
(defun monotux/re-seq (regexp string)
  "Get a list of all REGEXP matches in a STRING."
  (save-match-data
    (let ((pos 0)
          matches)
      (while (string-match regexp string pos)
        (let ((payee (match-string 1 string)))
          (unless (member payee matches)
            (push (match-string 1 string) matches)))
        (setq pos (match-end 0)))
      matches)))

(defun monotux/get-timeclock-candidates (buffer)
  "Extracts available payee candidates from provided BUFFER."
  (let ((regex-payee "^i [0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} [0-9]\\{2\\}:[0-9]\\{2\\}:[0-9]\\{2\\} \\(.*\\)$"))
    (monotux/re-seq regex-payee buffer)))

(defun monotux/check-in ()
  "Checka in på jobbet!"
  (interactive)
  (when (derived-mode-p 'ledger-mode)
    (let* ((pbuf (current-buffer))
           (clock-in-time (concat (read-string "När kom du till jobbet? (HH:MM) ") ":00"))
           (current-date (format-time-string "%Y-%m-%d"))
           complete-line)
      (dolist (e `("i" ,current-date ,clock-in-time))
        (add-to-list 'complete-line e t))
      (let* ((payee-candidates (monotux/get-timeclock-candidates (buffer-string)))
             (payee-chosen (completing-read "Vilket konto? " payee-candidates)))
        (add-to-list 'complete-line payee-chosen t))
      (goto-char (point-max))
      (insert (concat "\n" (string-join complete-line " ") "\n")))))

(defun monotux/check-out ()
  "Checka ut också."
  (interactive)
  (when (derived-mode-p 'ledger-mode)
    (let ((clock-out-time (concat (read-string "När checkade du ut? (HH:MM) ") ":00"))
          (current-date (format-time-string "%Y-%m-%d"))
          complete-line)
      (dolist (e `("o" ,current-date ,clock-out-time))
        (add-to-list 'complete-line e t))
      (goto-char (point-max))
      (insert (concat (string-join complete-line " ") "\n")))))

För att sammanställa en veckorapport av min tid kan en använda en kommandorad som nedan:

$ ledger -f time.ledger --daily --period "this week" reg
19-Aug-23 - 19-Aug-23    (Agio:InternTid)       4.00h     4.00h

Det roliga kommer givetvis när en har fler rapporter över fler dagar. Men det lämnas som en övning för läsaren att sammanställa själv! 🙂