;;; workout-tracker.el -- Workout Tracker ;; ;;; Copyright (C) 2008 Kyle W T Sherman ;; ;; Author: Kyle W T Sherman <kylewsherman at gmail dot com> ;; Created: 2008-06-19 ;; Version: 0.1 ;; Keywords: workout track gym ;; ;; This file is not part of GNU Emacs. ;; ;; This is free software; you can redistribute it and/or modify it under the ;; terms of the GNU General Public License as published by the Free Software ;; Foundation; either version 2, or (at your option) any later version. ;; ;; This is distributed in the hope that it will be useful, but WITHOUT ANY ;; WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS ;; FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ;; details. ;; ;; You should have received a copy of the GNU General Public License along ;; with GNU Emacs; see the file COPYING. If not, write to the Free Software ;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ;; ;;; Commentary: ;; ;; Provides `workout-tracker' function that allows for the entry of workout ;; data (routine, weight, repetitions, etc.), with tracking over time. ;; ;;; Installation: ;; ;; Put `workout-tracker.el' where you keep your elisp files and add something like ;; the following to your .emacs file: ;; ;; (require 'workout-tracker) ;; ;;; Usage: ;; ;; ;; Example: ;; ;; (workout-tracker ... ??? ;;; Code: ;; widget (require 'widget) ;; customize group (defgroup workout-tracker nil "Workout tracker." :prefix "workout-tracker-" :group 'applications) ;; database (defvar workout-tracker-data nil "Buffer local database of workout tracking data.") (make-variable-buffer-local 'workout-tracker-data) ;; data file (defvar workout-tracker-data-file nil "Buffer local data file (to store workout data).") (make-variable-buffer-local 'workout-tracker-data-file) ;; data file extension (defvar workout-tracker-data-file-extension ".wtd" "Data file extension, including period.") ;; data file extension match (defvar workout-tracker-data-file-extension-match (concat (replace-regexp-in-string "\\." "\\\\\." workout-tracker-data-file-extension) "$") "Data file extension match.") ;; data dir (defcustom workout-tracker-data-dir (expand-file-name "~/.workout-tracker") "Directory to store workout data files." :type 'directory :group 'workout-traker) ;; workout tracker buffer name prefix (defvar workout-tracker-buffer-name-prefix "workout-tracker" "Buffer name (prefix) to use for workout tracker.") ;; select server buffer name (defvar workout-tracker-select-server-buffer-name "*Workout-Tracker-Select-Server*" "Buffer name to use for select server menu.") ;; workout-tracker-buffers (defvar workout-tracker-buffers nil "Association list of active workout tracker buffers and their files. \nList is in the following format: ((FILE . BUFFER) ...)") ;; set data macro (defmacro workout-tracker-set-data (data) "Set local `workout-tracker-data' to DATA." `(setq workout-tracker-data ,data)) ;; file name macro (defmacro workout-tracker-file-name (file) "Returns FILE sans directory and extension parts." `(file-name-sans-extension (file-name-nondirectory ,file))) ;; set data file macro (defmacro workout-tracker-set-data-file (file) "Set local `workout-tracker-data-file' to FILE." `(setq workout-tracker-data-file ,file)) ;; tracker buffer name (defun workout-tracker-buffer-name (file) "Generate buffer name from `workout-tracker-buffer-name-prefix' and FILE." (concat workout-tracker-buffer-name-prefix ":" (workout-tracker-file-name file))) ;; set data file extension (defun workout-tracker-set-data-file-extension (extension) "Set `workout-tracker-data-file-extension' and `workout-tracker-data-file-extension-match'." (setq workout-tracker-data-file-extension extension) (setq workout-tracker-data-file-extension-match (concat (replace-regexp-in-string "\\." "\\\\\." workout-tracker-data-file-extension) "$"))) ;; switch to data file ;;;###autoload (defun workout-tracker-switch-to-data-file (file) "Return buffer of and switches to an existing data FILE. \nReturn nil if not found." (interactive "F") (let ((buffer (assoc file workout-tracker-buffers))) (when buffer (switch-to-buffer (cdr buffer)) (cdr buffer)))) ;; load data file ;;;###autoload (defun workout-tracker-load-data-file (file) "Load data FILE." (interactive "F") (let ((file (expand-file-name file)) data buffer) ;; attempt to switch to an existing data file buffer (unless (workout-tracker-switch-to-data-file file) (if (file-exists-p file) (progn (with-temp-buffer ;; load file into temp buffer (insert-file-contents file) ;; persist contents (setq data (buffer-substring-no-properties (point-min) (point-max)))) ;; create workout tracker buffer ;;(setq buffer (generate-new-buffer (workout-tracker-buffer-name file))) (setq buffer (get-buffer-create (workout-tracker-buffer-name file))) (switch-to-buffer buffer) ;; set data file (workout-tracker-set-file file) ;; copy file contents into local database (workout-tracker-set-data (eval data))) (message "Data file not found: %s" file))))) ;; save data file ;;;###autoload (defun workout-tracker-save-data-file (file) "Save data FILE." (interactive "F") ;; create directory if needed (let ((dir (file-name-directory file))) (unless (file-directory-p dir) (make-directory dir t))) (let ((file (expand-file-name file))) (with-temp-buffer ;; set data file (workout-tracker-set-file file) ;; write database into temp buffer (when (and (boundp 'workout-tracker-database) workout-tracker-database) (insert workout-tracker-database)) ;; save workout-tracker-database contents to file (write-file file)))) ;; create data file ;;;###autoload (defun workout-tracker-create-data-file (file) "Create empty data FILE. \nIf FILE already exists, user is prompted to overwrite." (interactive "F") ;; TODO: check for unsaved changes ;; attempt to switch to an existing data file buffer (unless (workout-tracker-switch-to-data-file file) (with-temp-buffer ;; set data file (workout-tracker-set-file file) ;; clear database (workout-tracker-set-data nil) ;; save empty data file (workout-tracker-save-data-file file)))) ;; open data file ;;;###autoload (defun workout-tracker-open-data-file (file) "Open data FILE." (interactive "F") ;; attempt to switch to an existing data file buffer (unless (workout-tracker-switch-to-data-file file) ;; load data file from disk (workout-tracker-load-data-file file) ;; TODO: open file in buffer ) ;; list dir files (defun workout-tracker-dir-files (dir &optional match) "List all files in directory DIR that match pattern MATCH." ;; expand dir to full path (setq dir (expand-file-name dir)) ;; make sure dir is a directory ;; (unless (file-directory-p dir) ;; (error (format "`%s' is not a directory" dir))) (when (file-directory-p dir) ;; set default match if none given (let ((match (or match ".*")) items ; items list to populate (files (nreverse (directory-files dir t)))) ; files in dir ;; loop through files (dolist (file files) ;; is item accessable? (when (file-readable-p file) ;; branch on type of item (cond ;; ignore `.' and `..' ((string-match "^\\.\\.?$" (file-name-nondirectory file)) t) ;; matching file or directory ((string-match match file) ;; only add files (unless (file-directory-p file) ;; file (let ((file-name (file-name-sans-extension (file-name-nondirectory file)))) ;; add file (push (list file-name file) items))) t)))) items))) ;; workout tracker (main method) ;;;###autoload (defun workout-tracker (&optional file) "Start workout tracking. \nIf FILE is given, use that workout data file. Otherwise, call `workout-tracker-select-data-file'." (interactive) (if file (workout-tracker-open-data-file file) (workout-tracker-select-data-file))) ;; select data file ;;;###autoload (defvar widget-data-file) (defun workout-tracker-select-data-file () "Present a list of data files the user may select from." (interactive) ;; setup buffer ;;(setq buffer (generate-new-buffer workout-tracker-select-server-buffer-name)) (when (get-buffer workout-tracker-select-server-buffer-name) (kill-buffer workout-tracker-select-server-buffer-name)) (let ((buffer (get-buffer-create workout-tracker-select-server-buffer-name))) (set-buffer buffer) (kill-all-local-variables) ;; add header (widget-insert "Workout Tracker\n\n") (widget-insert "Select an existing data file or create a new one:\n\n") ;; add existing data file list (let ((files (workout-tracker-dir-files workout-tracker-data-dir workout-tracker-data-file-extension-match))) (dolist (file files) (widget-create 'push-button :value (car file) :notify (lambda (widget &rest ignore) (let* ((value (widget-value widget)) ;; set file to dir + value + extension (file (expand-file-name (concat value workout-tracker-data-file-extension) workout-tracker-data-dir))) (kill-buffer nil) ;; open data file (workout-tracker-open-data-file file)))) (widget-insert "\n")) (widget-insert "\n")) ;; use local variable to store data file (make-local-variable 'widget-data-file) ;; add create option (widget-insert "Data File: ") (widget-create 'editable-field :size 40 :notify (lambda (widget &rest ignore) (let* ((value (widget-value widget)) (len-value (length value)) (len-ext (length workout-tracker-data-file-extension))) ;; set file to dir + value + extension (setq widget-data-file (expand-file-name (concat value workout-tracker-data-file-extension) workout-tracker-data-dir)) ;; remove extension if given (when (and (> len-value len-ext) (string= (substring value (- 0 len-ext)) workout-tracker-data-file-extension)) (setq widget-data-file (substring file 0 (- 0 len-ext))))))) (widget-insert " ") (widget-create 'push-button :value "Create" :notify (lambda (widget &rest ignore) (let ((file widget-data-file)) (if (file-exists-p file) (message "File already exists: %s" file) (progn (kill-buffer nil) ;; create new data file (workout-tracker-create-data-file file)))))) (widget-insert "\n") ;; final setup (use-local-map widget-keymap) (widget-setup) (switch-to-buffer buffer) (goto-char (point-min)) (widget-forward 1)))) (provide 'workout-tracker) ;;; workout-tracker.el ends here