~swflint/track-calorie-values

b252a5b85f85773d19f8b1c902b280e95cdfa5d7 — Samuel W. Flint 2 months ago 0938a70
Add Track-Values version
1 files changed, 144 insertions(+), 0 deletions(-)

A track-values.el
A track-values.el => track-values.el +144 -0
@@ 0,0 1,144 @@
;;; track-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-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-values-")

(defcustom track-values-save-file
  (locate-user-emacs-file "track-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-values-tables (list)
  "List of known track-values tables.")

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

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

(defvar track-values-current-key nil
  "Current item being recorded.")


;;; Table creation

(defun track-values-make-table (name &optional pairs)
  (pushnew name track-values-tables)
  (let ((table-name (intern (format "track-values-%s-table" name)))
        (prompt-name (intern (format "track-values-%s-keys" name))))
    (set table-name (make-hash-table :weakness nil :test 'case-fold))
    (set prompt-name (mapcar #'first pairs))
    (mapcar #'(lambda (x) (puthash (first x) (second x) (symbol-value table-name))) pairs)
    (setf track-values-modified-p t)))


;;; Get and set values

(defun track-values-get-value (table key)
  (gethash key (symbol-value (intern (format "track-values-%s-table" table)))))

(defun track-values-set-value (table key value)
  (puthash key value (symbol-value (intern (format "track-values-%s-table" table))))
  (cl-pushnew key (symbol-value (intern (format "track-values-%s-keys" table))) :test #'string=)
  (setf track-values-modified-p t))


;;; I/O functions

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

(defun track-values-save-data ()
  "Write calorie data."
  (when track-values-modified-p
    (with-temp-buffer
      (insert ";; generated by track-values.el        -*- mode: lisp-data -*-\n"
              ";; do not modify by hand\n\n")
      (let ((standard-output (current-buffer)))
        (mapcar #'(lambda (name)
                    (print `(track-values-make-table ,name ',(mapcar (lambda (x)
                                                                       (list x (track-values-get-value name x)))
                                                                     (hash-table-keys (symbol-value (intern (format "track-values-%s-table" name))))))))
                track-values-tables))
      (write-region (point-min) (point-max) track-values-save-file)
      (setf track-values-modified-p nil))))


;;; Primary interaction functions

(defun track-values-complete (&optional name)
  (let ((all-keys (remove-duplicates (loop for name in track-values-tables appending (symbol-value (intern (format "track-values-%s-keys" name)))))))
    (if name
        (completing-read "Item: " (symbol-value (intern (format "track-values-%s-keys" name)))
                         #'identity nil nil nil nil t)
      (completing-read "Item: " all-keys
                       #'identity nil nil nil nil t))))

(defun track-values-start (&optional name)
  "Get a food item to retrieve or prompt for calories."
  (unless track-values-loaded-p
    (track-values-load-data))
  (let ((key (track-values-complete name)))
    (if key
        (setf track-values-current-key (downcase key))
      (user-error "A key must be provided"))))

(defun track-values-insert (name)
  "Get or prompt for the number of calories in the current food item."
  (let ((item (track-values-get-value name track-values-current-key)))
    (if item
        (format "%s" item)
      (let ((item (read-string (format "%s in \"%s\": " name track-values-current-key))))
        (track-values-set-key name track-values-current-key item)
        (format "%s" item)))))

(defun track-values-end ()
  (setf track-values-current-key nil))

(provide 'track-values)

;;; track-values.el ends here