This repository has been archived on 2022-05-13. You can view files and clone it, but cannot push or open issues or pull requests.
chronometrist/chronometrist-common.el

195 lines
7.0 KiB
EmacsLisp

;;; chronometrist-common.el --- Common definitions for Chronometrist -*- lexical-binding: t; -*-
;;; Commentary:
;;
(require 'dash)
(require 'cl-lib)
(require 'chronometrist-time)
;; ## VARIABLES ##
;;; Code:
(defvar chronometrist-empty-time-string "-")
(defvar chronometrist-date-re "[0-9]\\{4\\}/[0-9]\\{2\\}/[0-9]\\{2\\}")
(defvar chronometrist-time-re-ui
(rx-to-string
`(or
(and (repeat 0 2
(optional (repeat 1 2 digit) ":"))
(repeat 1 2 digit))
,chronometrist-empty-time-string))
"Regular expression to represent a timestamp in `chronometrist'.
This is distinct from `chronometrist-time-re-file' (which see) -
`chronometrist-time-re-ui' is meant for the user interface, and
must correspond to the output from `chronometrist-format-time'.")
(defvar chronometrist-time-re-file "[0-9]\\{2\\}:[0-9]\\{2\\}:[0-9]\\{2\\}"
"Regular expression to represent a timestamp in the file `timeclock-file'.
This is distinct from `chronometrist-time-re-ui' (which see).")
(defun chronometrist-buffer-exists? (buffer-name)
"Return non-nil if BUFFER-NAME exists."
(--> (buffer-list)
(mapcar #'buffer-name it)
(member buffer-name it)))
(defun chronometrist-buffer-visible? (buffer-or-buffer-name)
"Return t if BUFFER-OR-BUFFER-NAME is visible to user."
;; It'd be simpler to use only the windows of the current frame (-->
;; (selected-frame) (window-list it) ...) - but it wouldn't be
;; robust, because it is possible that a frame partially covers
;; another and the buffer is visible to the user from the latter.
(let ((result (-->
(visible-frame-list)
(mapcar #'window-list it)
(mapcar (lambda (list)
(mapcar #'window-buffer list))
it)
(mapcar (lambda (list)
(mapcar (lambda (buffer)
(if (bufferp buffer-or-buffer-name)
(equal buffer-or-buffer-name buffer)
(equal (buffer-name buffer)
buffer-or-buffer-name)))
list))
it)
(mapcar (lambda (list)
(-filter #'identity list))
it)
(mapcar #'car it)
(car it))))
(if result t nil)))
(defun chronometrist-get-end-time (target-date)
"Return the timestamp of the next clock-out event after point.
This is meant to be run in `timeclock-file'.
If there is no clock-out event after point, return the current
date and time.
If the clock-out time is past midnight, return the date in
TARGET-DATE with the time at midnight (\"24:00:00\").
Return value is a string in the form \"YYYY/MM/DD HH:MM:SS\"
TARGET-DATE must be a date in the form \"YYYY/MM/DD\"
Point must be on a clock-in event having the same date as
TARGET-DATE."
(declare (obsolete nil "Chronometrist v0.3.0"))
(let* ((date-time (if (progn
(forward-line)
(beginning-of-line)
(looking-at-p "^o "))
(progn
(re-search-forward "o ")
(buffer-substring-no-properties (point)
(+ 10 1 8 (point))))
(format-time-string "%Y/%m/%d %T")))
(date-time-list (chronometrist-timestamp->list date-time))
(target-date-list (chronometrist-timestamp->list target-date)))
(if (chronometrist-time-interval-span-midnight? target-date-list date-time-list)
(concat target-date " 24:00:00")
date-time)))
;; tests -
;; (mapcar #'chronometrist-format-time
;; '((0 0 0) (0 0 1) (0 0 10) (0 1 10) (0 10 10) (1 10 10) (10 10 10)))
;; => ("" "00:01" "00:10" "01:10" "10:10" "01:10:10" "10:10:10")
;; (mapcar #'chronometrist-format-time
;; '([0 0 0] [0 0 1] [0 0 10] [0 1 10] [0 10 10] [1 10 10] [10 10 10]))
;; => ("" "00:01" "00:10" "01:10" "10:10" "01:10:10" "10:10:10")
(defun chronometrist-format-time (duration)
"Format DURATION as a string suitable for display in Chronometrist buffers.
DURATION must be a vector or a list of the form [HOURS MINUTES
SECONDS] or (HOURS MINUTES SECONDS)."
(let ((h (elt duration 0))
(m (elt duration 1))
(s (elt duration 2))
(blank " "))
(if (and (zerop h) (zerop m) (zerop s))
" -"
(let ((h (if (zerop h)
blank
(format "%2d:" h)))
(m (cond ((and (zerop h)
(zerop m))
blank)
((zerop h)
(format "%2d:" m))
(t
(format "%02d:" m))))
(s (if (and (zerop h)
(zerop m))
(format "%2d" s)
(format "%02d" s))))
(concat h m s)))))
(defun chronometrist-open-file (&optional button)
"Open `chronometrist-file' in another window.
Argument BUTTON is for the purpose of using this command as a
button action."
(interactive)
(find-file-other-window chronometrist-file)
(goto-char (point-max)))
(defun chronometrist-common-create-chronometrist-file ()
"Create `chronometrist-file' if it doesn't already exist."
(unless (file-exists-p chronometrist-file)
(with-current-buffer (find-file-noselect chronometrist-file)
(write-file chronometrist-file))))
(defun chronometrist-common-file-empty-p (file)
"Return t if FILE is empty."
(let ((size (elt (file-attributes file) 7)))
(if (zerop size) t nil)))
(defun chronometrist-common-clear-buffer (buffer)
"Clear the contents of BUFFER."
(with-current-buffer buffer
(goto-char (point-min))
(delete-region (point-min) (point-max))))
(defun chronometrist-date-op-internal (seconds minutes hours day month year operator count)
"Helper function for `chronometrist-date-op'."
(declare (obsolete nil "Chronometrist v0.3.0"))
(-->
(encode-time seconds minutes hours day month year)
(funcall (cond ((equal operator '+) 'time-add)
((equal operator '-) 'time-subtract)
(t (error "Unknown operator %s" operator)))
it
(list 0 (* 86400 count)))
(decode-time it)))
(defun chronometrist-format-keybinds (command map &optional firstonly)
"Return the keybindings for COMMAND in MAP as a string.
If FIRSTONLY is non-nil, return only the first keybinding found."
(if firstonly
(key-description
(where-is-internal command map firstonly))
(->> (where-is-internal command map)
(mapcar #'key-description)
(-take 2)
(-interpose ", ")
(apply #'concat))))
(defvar chronometrist--fs-watch nil
"Filesystem watch object.
Used to prevent more than one watch being added for the same
file.")
;; Local Variables:
;; nameless-current-name: "chronometrist-common"
;; End:
(provide 'chronometrist-common)
;;; chronometrist-common.el ends here