;;; track-calorie-values.el --- Track calorie values for various foods ;; Copyright (C) 2020 Samuel W. Flint ;; author: Samuel W. Flint ;; 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 (with-temp-buffer (insert ";; generated by track-calorie-values.el -*- mode: lisp-data -*-\n" ";; do not modify by hand\n\n") (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) (setf track-calorie-values-modified-p nil)))) ;;; 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