summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDuncan Wilkie <antigravityd@gmail.com>2023-11-01 02:06:47 -0500
committerDuncan Wilkie <antigravityd@gmail.com>2023-11-01 02:06:47 -0500
commit2f7b2da8e7633d677907094090d65d2dd2a42f43 (patch)
treef6ea6b1aa857943f12733c2f8a4b1a89e9705b1b
parent014e984736e06184141787ef5fc4437e2ca00e8f (diff)
Added post about forms.el use
-rw-r--r--dnw/forms.scm1
-rw-r--r--org/.dir-locals.el5
-rw-r--r--org/haunt/haunt.org2
-rw-r--r--org/lift-form/lift-form.org190
-rw-r--r--org/lift-form/lift-form.org~178
5 files changed, 373 insertions, 3 deletions
diff --git a/dnw/forms.scm b/dnw/forms.scm
index ba6d5a2..1dac7ba 100644
--- a/dnw/forms.scm
+++ b/dnw/forms.scm
@@ -219,3 +219,4 @@ SETQ < 'setq'")
;; TODO: explain that check-number-of-fields must be set and each field the correct size.
;; TODO: add option for newline replacement character re-substitution.
;; TODO: consider refactoring to build HTML trees, properly classed for styling and pruning purposes, rather than a raw string.
+;; TODO: use Guile Hoot for data entry!
diff --git a/org/.dir-locals.el b/org/.dir-locals.el
index d00959c..23bcdda 100644
--- a/org/.dir-locals.el
+++ b/org/.dir-locals.el
@@ -1,4 +1,7 @@
;;; Directory Local Variables
;;; For more information see (info "(emacs) Directory Variables")
-((org-mode . ((eval . (add-hook 'after-save-hook #'ox-haunt-export-to-html 0 t)))))
+((org-mode . ((time-stamp-start . "#\\+DATE:[ ]+\\\\?[\"<]+")
+ (time-stamp-format . "%Y-%02m-%02d %3a %02H:%02M")
+ (eval . (progn (add-hook 'after-save-hook #'time-stamp)
+ (add-hook 'after-save-hook #'ox-haunt-export-to-html 0 t))))))
diff --git a/org/haunt/haunt.org b/org/haunt/haunt.org
index 78741b0..979c26d 100644
--- a/org/haunt/haunt.org
+++ b/org/haunt/haunt.org
@@ -1,5 +1,3 @@
-# -*- after-save-hook: (ox-haunt-export-to-html); -*-
-
#+TITLE: Building a Site with Haunt
#+TAGS: Programming, Lisp, Web, Emacs, Org
#+DATE: <2023-06-27 Tue>
diff --git a/org/lift-form/lift-form.org b/org/lift-form/lift-form.org
new file mode 100644
index 0000000..6f31c73
--- /dev/null
+++ b/org/lift-form/lift-form.org
@@ -0,0 +1,190 @@
+# -*- org-export-use-babel: nil;-*-
+
+#+TITLE: Tracking Physical Performance, Personal Library with Emacs
+#+DATE: <2023-11-01 Wed 00:42>
+#+TAGS: Emacs, Fitness, Data, Forms.el
+
+I've just started making systematic efforts in my physical fitness. Following some [[https://rpstrength.com/hypertrophy-training-guide-central-hub/][work I've seen on periodized hypertrophy training]], I planned out a mesocycle in my usual Android Notes, but noticed my systematic representation of workout data could much more ergonomically be represented as a forms file. The data entry is better, and data analysis is easier! Realizing just how good it is, I also implemented some basic library management in it for my physical books.
+
+[[https://www.gnu.org/software/emacs/manual/html_mono/forms.html][Forms Mode]] is a little-known, built-in corner of Emacs that makes reading and writing delimiter-separated value data a much more pleasant experience. It enables the end-user to create a text-based interface to the data, using a more primitive [[https://www.gnu.org/software/emacs/manual/html_node/autotype/index.html][skeleton]]-like system to produce a buffer for each row in the file. Each field is editable, but the ambient text (that perhaps explains the semantics of the data fields) is read-only. I've found all its shortcomings reducible to those of delimiter-separated value data itself; for applications where that suffices, it really is an excellent tool.
+
+* Building a Forms File
+
+To show and not tell, here's the forms control file I've whipped up to track exercises. It's slightly more complicated than is customary, because of the metaprogramming needed to economically implement the repeated per-exercise structure (this is one of those shortcomings of delimiter-separated value data).
+
+#+begin_src emacs-lisp :eval never-export
+
+ ;; Increase if you want, but it's probably not a good biomechanical idea.
+ (setq max-exercises 10)
+
+ (setq forms-file "lifts.tsv" ;; Name of the file the data is to be stored in.
+
+ ;; Specially-interpreted list, describing the textual interface to the data.
+ forms-format-list `("====== Lift Session ======\n\n"
+ ;; Basic info.
+ type " day, on " date ".\n"
+ "Block " block ", mesocycle " mesocycle
+ ", week " week ".\n\n"
+
+ "Exercise, (sets)x(reps) weight—improvement made. Notes.\n"
+ ,@(apply
+ 'append
+ (mapcar (lambda (ex-number)
+ (let ((name (intern (concat "ex" (number-to-string ex-number) "-name")))
+ (sets (intern (concat "ex" (number-to-string ex-number) "-sets")))
+ (reps (intern (concat "ex" (number-to-string ex-number) "-reps")))
+ (weight (intern (concat "ex" (number-to-string ex-number) "-weight")))
+ (improved (intern (concat "ex" (number-to-string ex-number) "-improved")))
+ (notes (intern (concat "ex" (number-to-string ex-number) "-notes"))))
+ (list name ", " sets"x"reps " " weight "—" improved ". " notes "\n")))
+ (number-sequence 1 max-exercises)))
+
+ "\nAftermath:\n"
+ aftermath)
+
+ ;; The numbers of each field is associated with a corresponding symbol, for use in the above.
+ forms-number-of-fields (forms-enumerate
+ `(type
+ date
+ block
+ mesocycle
+ week
+ ,@(apply
+ 'append
+ (mapcar (lambda (ex-number)
+ (let ((name (intern (concat "ex" (number-to-string ex-number) "-name")))
+ (sets (intern (concat "ex" (number-to-string ex-number) "-sets")))
+ (reps (intern (concat "ex" (number-to-string ex-number) "-reps")))
+ (weight (intern (concat "ex" (number-to-string ex-number) "-weight")))
+ (improved (intern (concat "ex" (number-to-string ex-number) "-improved")))
+ (notes (intern (concat "ex" (number-to-string ex-number) "-notes"))))
+ (list name sets reps weight improved notes)))
+ (number-sequence 1 max-exercises)))
+ aftermath))
+
+ ;; Bookkeeping about the data file; various unused data integrity checking features.
+ forms-field-sep "\t"
+ forms-read-only nil
+ forms-multi-line " "
+ forms-read-file-filter nil
+ forms-write-file-filter nil
+ forms-new-record-filter nil
+ forms-insert-after nil
+ forms-check-number-of-fields t)
+
+#+end_src
+
+Here's the one for the library:
+
+#+begin_src emacs-lisp :eval never-export
+
+ (setq forms-file "library.tsv"
+ forms-format-list '("====== Library Book ======\n\n"
+ ;; Basic info.
+ "\"" title "\""
+ " by " author ".\n"
+ "Description:\n" description "\n"
+ "\n\n"
+ ;; Publication information.
+ "Edition " edition
+ (if (string-equal (nth volume forms-fields) "")
+ " "
+ ", volume ")
+ volume
+ (if (string-equal (nth volume forms-fields) "")
+ ""
+ "; ")
+
+ "published by " publisher ", "
+
+ (if (string-equal (nth series forms-fields) "")
+ ""
+ "in the ")
+ series
+ (if (string-equal (nth series forms-fields) "")
+ ""
+ " series, ")
+
+ "in " year "."
+ "\n\n"
+ ;; Physical information.
+ "Hardcover: " hardcover? "\n"
+ "Jacket: " jacket? "\n"
+ condition " condition; "
+ page-count " pages.\n"
+ "Last location: " location
+ "\n\n"
+ ;; Classification information.
+ "ISBN-13: " isbn-13
+ "\nLC Classification: " lc
+ ;; If you're the type to download a car,
+ ;; maybe put things like shadow archive hashes here too.
+ "\n\n"
+ ;; My relationship with it.
+ "Reading status " status ": "
+ (cond ((string-equal (nth status forms-fields) "0") "On the wishlist.")
+ ((string-equal (nth status forms-fields) "1") "Unread.")
+ ((string-equal (nth status forms-fields) "2") "Skimmed a little.")
+ ((string-equal (nth status forms-fields) "3") "Read at surface level.")
+ ((string-equal (nth status forms-fields) "4") "Read a good chunk in depth.")
+ ((string-equal (nth status forms-fields) "5") "Completely mastered.")
+ (t ""))
+
+ "\nBought from " purchase-from
+ ", " purchase-date ".\n"
+ "Opinion:\n" opinion)
+ forms-number-of-fields (forms-enumerate
+ '(title
+ author
+ publisher
+ series
+ edition
+ volume
+ year
+ page-count
+ condition
+ hardcover?
+ jacket?
+ location
+ isbn-13
+ lc
+ status
+ purchase-from
+ purchase-date
+ opinion
+ description))
+ forms-field-sep "\t"
+ forms-read-only nil
+ forms-multi-line " "
+ forms-read-file-filter nil
+ forms-write-file-filter nil
+ forms-new-record-filter nil
+ forms-insert-after nil
+ forms-check-number-of-fields t)
+
+#+end_src
+
+To use these, just plop the control file in a directory, call =M-x forms-find-file= on it, and enter your data! =TAB= and =S-TAB= move forward and backward through field locations for editing; several normal Emacs keybindings operate at a meta level if prefixed by =C-c= , e.g. =C-c C-n= and =C-c C-p= move forward and backward through the records, and you can search the records for text with =C-c C-s= and =C-c C-r=. =C-c C-o= inserts a new record.
+
+* Data Analysis
+
+The data stored may be consumed by analysis workflows in whatever language you want, within Emacs, with the great power of Org Babel. If you create an Org file in the same directory as the data and control files, you can put in a code block e.g.
+
+#+begin_src gnuplot
+
+ set terminal png
+ set output "plot.png"
+
+ set xlabel "X Axis"
+ set ylabel "Y Axis"
+ plot "forms.tsv"
+
+#+end_src
+
+The resulting image can be embedded directly in the buffer by linking to it, resulting in dynamically-updating visualizations upon re-evaluating the code block! Intermediate computational results can be shared between snippets of different programming languages; analysis can be done in R, Python, and really most other programming languages you might want to use.
+
+* Distributed Entry
+
+Currently, the biggest shortcoming of this approach to tracking is that it's limited by the provenance of Emacs. However, with Emacs (possibly) coming to Android in release 30, this is no longer a restriction, and you can enter data from your phone no problem. Synchronizing the data is as simple as tossing them under Git version control, and setting the remote to your VPS with a public IP.
+
+I have a project going that currently translates form control files to Scheme, which I use to generate the [[https://functorial.xyz/pages/influences.html][library book list]] for this website. My hope is, eventually, to re-implement much of the forms.el functionality using Guile Hoot, and enable browser data entry to forms files! The source for this can be found among the source for this website (see the page footer).
diff --git a/org/lift-form/lift-form.org~ b/org/lift-form/lift-form.org~
new file mode 100644
index 0000000..712b1fc
--- /dev/null
+++ b/org/lift-form/lift-form.org~
@@ -0,0 +1,178 @@
+# -*- org-export-use-babel: nil;-*-
+
+#+TITLE: Tracking Physical Performance, Personal Library with Emacs
+#+DATE: <2023-11-01 Wed 00:42>
+#+TAGS: Emacs, Fitness, Data, Forms.el
+
+I've just started making systematic efforts in my physical fitness. Following some [[https://rpstrength.com/hypertrophy-training-guide-central-hub/][work I've seen on periodized hypertrophy training]], I planned out a mesocycle in my usual Android Notes, but noticed my systematic representation of workout data could much more ergonomically be represented as a forms file. The data entry is better, and data analysis is easier! Realizing just how good it is, I also implemented some basic library management in it for my physical books.
+
+[[https://www.gnu.org/software/emacs/manual/html_mono/forms.html][Forms Mode]] is a little-known, built-in corner of Emacs that makes reading and writing delimiter-separated value data a much more pleasant experience. It enables the end-user to create a text-based interface to the data, using a more primitive [[https://www.gnu.org/software/emacs/manual/html_node/autotype/index.html][skeleton]]-like system to produce a buffer for each row in the file. Each field is editable, but the ambient text (that perhaps explains the semantics of the data fields) is read-only. I've found all its shortcomings reducible to those of delimiter-separated value data itself; for applications where that suffices, it really is an excellent tool.
+
+* Building a Forms File
+
+To show and not tell, here's the forms control file I've whipped up to track exercises. It's slightly more complicated than is customary, because of the metaprogramming needed to economically implement the repeated per-exercise structure (this is one of those shortcomings of delimiter-separated value data).
+
+#+begin_src emacs-lisp :eval never-export
+
+ ;; Increase if you want, but it's probably not a good biomechanical idea.
+ (setq max-exercises 10)
+
+ (setq forms-file "lifts.tsv" ;; Name of the file the data is to be stored in.
+
+ ;; Specially-interpreted list, describing the textual interface to the data.
+ forms-format-list `("====== Lift Session ======\n\n"
+ ;; Basic info.
+ type " day, on " date ".\n"
+ "Block " block ", mesocycle " mesocycle
+ ", week " week ".\n\n"
+
+ "Exercise, (sets)x(reps) weight—improvement made. Notes.\n"
+ ,@(apply
+ 'append
+ (mapcar (lambda (ex-number)
+ (let ((name (intern (concat "ex" (number-to-string ex-number) "-name")))
+ (sets (intern (concat "ex" (number-to-string ex-number) "-sets")))
+ (reps (intern (concat "ex" (number-to-string ex-number) "-reps")))
+ (weight (intern (concat "ex" (number-to-string ex-number) "-weight")))
+ (improved (intern (concat "ex" (number-to-string ex-number) "-improved")))
+ (notes (intern (concat "ex" (number-to-string ex-number) "-notes"))))
+ (list name ", " sets"x"reps " " weight "—" improved ". " notes "\n")))
+ (number-sequence 1 max-exercises)))
+
+ "\nAftermath:\n"
+ aftermath)
+
+ ;; The numbers of each field is associated with a corresponding symbol, for use in the above.
+ forms-number-of-fields (forms-enumerate
+ `(type
+ date
+ block
+ mesocycle
+ week
+ ,@(apply
+ 'append
+ (mapcar (lambda (ex-number)
+ (let ((name (intern (concat "ex" (number-to-string ex-number) "-name")))
+ (sets (intern (concat "ex" (number-to-string ex-number) "-sets")))
+ (reps (intern (concat "ex" (number-to-string ex-number) "-reps")))
+ (weight (intern (concat "ex" (number-to-string ex-number) "-weight")))
+ (improved (intern (concat "ex" (number-to-string ex-number) "-improved")))
+ (notes (intern (concat "ex" (number-to-string ex-number) "-notes"))))
+ (list name sets reps weight improved notes)))
+ (number-sequence 1 max-exercises)))
+ aftermath))
+
+ ;; Bookkeeping about the data file; various unused data integrity checking features.
+ forms-field-sep "\t"
+ forms-read-only nil
+ forms-multi-line " "
+ forms-read-file-filter nil
+ forms-write-file-filter nil
+ forms-new-record-filter nil
+ forms-insert-after nil
+ forms-check-number-of-fields t)
+
+#+end_src
+
+Here's the one for the library:
+
+#+begin_src emacs-lisp :eval never-export
+
+ (setq forms-file "library.tsv"
+ forms-format-list '("====== Library Book ======\n\n"
+ ;; Basic info.
+ "\"" title "\""
+ " by " author ".\n"
+ "Description:\n" description "\n"
+ "\n\n"
+ ;; Publication information.
+ "Edition " edition
+ (if (string-equal (nth volume forms-fields) "")
+ " "
+ ", volume ")
+ volume
+ (if (string-equal (nth volume forms-fields) "")
+ ""
+ "; ")
+
+ "published by " publisher ", "
+
+ (if (string-equal (nth series forms-fields) "")
+ ""
+ "in the ")
+ series
+ (if (string-equal (nth series forms-fields) "")
+ ""
+ " series, ")
+
+ "in " year "."
+ "\n\n"
+ ;; Physical information.
+ "Hardcover: " hardcover? "\n"
+ "Jacket: " jacket? "\n"
+ condition " condition; "
+ page-count " pages.\n"
+ "Last location: " location
+ "\n\n"
+ ;; Classification information.
+ "ISBN-13: " isbn-13
+ "\nLC Classification: " lc
+ ;; If you're the type to download a car,
+ ;; maybe put things like shadow archive hashes here too.
+ "\n\n"
+ ;; My relationship with it.
+ "Reading status " status ": "
+ (cond ((string-equal (nth status forms-fields) "0") "On the wishlist.")
+ ((string-equal (nth status forms-fields) "1") "Unread.")
+ ((string-equal (nth status forms-fields) "2") "Skimmed a little.")
+ ((string-equal (nth status forms-fields) "3") "Read at surface level.")
+ ((string-equal (nth status forms-fields) "4") "Read a good chunk in depth.")
+ ((string-equal (nth status forms-fields) "5") "Completely mastered.")
+ (t ""))
+
+ "\nBought from " purchase-from
+ ", " purchase-date ".\n"
+ "Opinion:\n" opinion)
+ forms-number-of-fields (forms-enumerate
+ '(title
+ author
+ publisher
+ series
+ edition
+ volume
+ year
+ page-count
+ condition
+ hardcover?
+ jacket?
+ location
+ isbn-13
+ lc
+ status
+ purchase-from
+ purchase-date
+ opinion
+ description))
+ forms-field-sep "\t"
+ forms-read-only nil
+ forms-multi-line " "
+ forms-read-file-filter nil
+ forms-write-file-filter nil
+ forms-new-record-filter nil
+ forms-insert-after nil
+ forms-check-number-of-fields t)
+
+#+end_src
+
+To use these, just plop the control file in a directory, call =M-x forms-find-file= on it, and enter your data! =TAB= and =S-TAB= move forward and backward through field locations for editing; several normal Emacs keybindings operate at a meta level if prefixed by =C-c= , e.g. =C-c C-n= and =C-c C-p= move forward and backward through the records, and you can search the records for text with =C-c C-s= and =C-c C-r=. =C-c C-o= inserts a new record.
+
+* Distributed Entry
+
+Currently, the biggest shortcoming of this approach to tracking is that it's limited by the provenance of Emacs. However, with Emacs (possibly) coming to Android in release 30, this is no longer a restriction, and you can enter data from your phone no problem. Synchronizing the data is as simple as tossing them under Git version control, and setting the remote to your VPS with a public IP.
+
+* Data Analysis
+
+The data stored may be consumed by analysis workflows in whatever language you want, within Emacs, with the great power of Org Babel. If you create an Org file in the same directory as the data and control files, you can, e.g.,
+
+
+That's all!