~swflint/track-calorie-values

5cf66664164d1ca957c9df1626ef232bd1ddea3f — Samuel W. Flint 5 months ago 26bbc3c
Add a very basic means of tracking the calorie values of things logged
1 files changed, 117 insertions(+), 0 deletions(-)

A track-calorie-values.el
A track-calorie-values.el => track-calorie-values.el +117 -0
@@ 0,0 1,117 @@
;;; track-calorie-values.el --- Track calorie values for various foods

;; Copyright (C) 2020 Samuel W. Flint <swflint@flintfam.org>

;; author: Samuel W. Flint <swflint@flintfam.org>
;; Version: 0.1
;; Keywords: diet, tracking
;; URL: https://git.sr.ht/~swflint/track-calorie-values

;; This file is NOT part of Emacs.
;;
;;  This file is licensed under the GNU GPLv3 or later.

;;; Commentary:
;;

;;; Code:

(require 'cl-lib)


;;; Customizeable values

(defgroup track-calorie-values nil
  "Track calorie values for various foods"
  :tag "Track Calorie Values"
  :group 'org
  :link '(url-link "https://git.sr.ht/~swflint/track-calorie-values")
  :prefix "track-calorie-values-")

(defcustom track-calorie-values-save-file
  (locate-user-emacs-file "track-calorie-values-data.el")
  "File to store calorie values of food items."
  :type '(file :must-match t))


;;; Hashing

;; Taken from emacs documentation
(defun case-fold-string= (a b)
  (eq t (compare-strings a nil nil b nil nil t)))
(defun case-fold-string-hash (a)
  (sxhash-equal (upcase a)))

(define-hash-table-test 'case-fold
  'case-fold-string= 'case-fold-string-hash)


;;; Internal Data
(defvar track-calorie-values-data (make-hash-table :weakness nil :test 'case-fold)
  "Storage for calorie values.

Keys are names, values are calories.")

(defvar track-calorie-values-foods nil
  "Storage for food names.")

(defvar track-calorie-values-loaded-p nil
  "Have calorie values been loaded?")

(defvar track-calorie-values-modified-p t
  "Have calorie values been modified?")

(defvar track-calorie-values-current-food nil
  "Current food being recorded.")


;;; I/O functions

(defun track-calorie-values-load-data ()
  "Load calorie data."
  (when (file-exists-p track-calorie-values-save-file)
    (load track-calorie-values-save-file t t t)
    (setf track-calorie-values-loaded-p t
          track-calorie-values-foods (hash-table-keys track-calorie-values-data)
          track-calorie-values-modified-p nil)))

(defun track-calorie-values-save-data ()
  "Write calorie data."
  (when track-calorie-values-modified-p
    (insert ";; generated by track-calorie-values.el        -*- mode: lisp-data -*-\n"
            ";; do not modify by hand\n")
    (with-temp-buffer
      (let ((standard-output (current-buffer)))
        (print `(setq track-calorie-values-data ,track-calorie-values-data)))
      (write-region (point-min) (point-max) track-calorie-values-save-file))))


;;; Primary interaction functions

(defun track-calorie-values-food ()
  "Get a food item to retrieve or prompt for calories."
  (unless track-calorie-values-loaded-p
    (track-calorie-values-load-data))
  (let ((food (completing-read "Food item: " track-calorie-values-foods
                               #'identity nil nil nil nil t)))
    (if food
        (setf track-calorie-values-current-food (downcase food))
      (user-error "A food name must be provided"))))

(defun track-calorie-values-calories ()
  "Get or prompt for the number of calories in the current food item."
  (let ((calories (gethash track-calorie-values-current-food track-calorie-values-data)))
    (if calories
        (progn
          (setf track-calorie-values-current-food nil)
          (format "%d" calories))
      (let ((calories (read-number (format "Calories in \"%s\": " track-calorie-values-current-food))))
        (puthash track-calorie-values-current-food calories track-calorie-values-data)
        (cl-pushnew track-calorie-values-current-food track-calorie-values-foods :test #'string=)
        (setf track-calorie-values-current-food nil
              track-calorie-values-modified-p t)
        (format "%d" calories)))))

(provide 'track-calorie-values)

;;; track-calorie-values.el ends here