branch: externals/org-mathsheet
commit d21148319761192b5761e93da7f932d956816c26
Author: Ian Martins <ia...@jhu.edu>
Commit: Ian Martins <ia...@jhu.edu>

    Set package dependencies, fix import warnings, and some minor changes
---
 README.md        | 142 ++++++++++++++++++++++++++++++++++++-------------------
 mathsheet.org    |  61 ++++++++++++++----------
 org-mathsheet.el |  43 ++++++++++-------
 3 files changed, 155 insertions(+), 91 deletions(-)

diff --git a/README.md b/README.md
index 705829085b..028a50b37f 100644
--- a/README.md
+++ b/README.md
@@ -126,7 +126,7 @@ Both are described in detail below.
     a worksheet, each worksheet is configured with a set of templates in a
     templates table. For example
     
-    <table id="org3c0f295" border="2" cellspacing="0" cellpadding="6" 
rules="groups" frame="hsides">
+    <table id="org63a6bd3" border="2" cellspacing="0" cellpadding="6" 
rules="groups" frame="hsides">
     
     
     <colgroup>
@@ -184,7 +184,7 @@ Both are described in detail below.
 
     Here is another example template table.
     
-    <table id="orga58bae4" border="2" cellspacing="0" cellpadding="6" 
rules="groups" frame="hsides">
+    <table id="org5f01db6" border="2" cellspacing="0" cellpadding="6" 
rules="groups" frame="hsides">
     
     
     <colgroup>
@@ -289,6 +289,10 @@ Both are described in detail below.
 
 This is the standard emacs package header.
 
+`emacs 26` is needed for `seq-random-elt`.  `calc` is used to solve the
+problems as well as converting them to mathematical notation in LaTeX
+format.
+
     ;;; org-mathsheet.el --- Generate dynamic math worksheets  -*- 
lexical-binding:t -*-
     
     ;; Copyright (C) 2025 Free Software Foundation, Inc.
@@ -297,7 +301,9 @@ This is the standard emacs package header.
     ;; Keywords: tools, education, math
     ;; Homepage: https://gitlab.com/ianxm/org-mathsheet
     ;; Version: 1.0
-    ;; Package-Requires: ((peg "1.0"))
+    ;; Package-Requires: ((peg "1.0")
+    ;;                    (emacs "26.0")
+    ;;                    calc)
     
     ;; This file is not part of GNU Emacs.
     
@@ -326,9 +332,16 @@ This is the standard emacs package header.
 
 ### Dependencies
 
-This package needs [peg](https://elpa.gnu.org/packages/peg.html).
+This package needs [peg](https://elpa.gnu.org/packages/peg.html). We also need 
[calc](https://www.gnu.org/software/emacs/manual/html_mono/calc.html) and some 
[org-table](https://orgmode.org/manual/Tables.html) and
+[org-babel](https://orgmode.org/org.html#Working-with-Source-Code) functions.
 
     (require 'peg)
+    (require 'calc)
+    
+    (declare-function math-read-expr "calc-ext")
+    (declare-function org-table-align "org-table")
+    (declare-function org-table-to-lisp "org-table")
+    (declare-function org-babel-named-data-regexp-for-name "ob-core")
 
 
 ### Variables
@@ -340,10 +353,10 @@ worksheet, which is defined in a LaTeX source block 
below. This
 assigns the constant directly to that named block.
 
     (defvar org-mathsheet--var-list '()
-      "List of variables used in a problem")
+      "List of variables used in a problem.")
     
     (defconst org-mathsheet--worksheet-template page
-      "LaTeX template for the worksheet")
+      "LaTeX template for the worksheet.")
 
 
 ### Scan problem
@@ -388,11 +401,11 @@ of `open-fields`.
       "Scan a problem.
     
     This parses the problem and produces a list containing info about
-    its fields. For each field it returns a list containing:
+    its fields.  For each field it returns a list containing:
     1. a symbol for the assigned variable or a unique placeholder
     2. a list of variables this field depends on
     3. a cons containing start and end markers for the field in the current 
buffer
-    4. `nil' which is used by `dfs-visit' later"
+    4. nil which is used by `dfs-visit' later"
       (let ((field-index 0)
             open-fields ; stack
             closed-fields ; list
@@ -439,7 +452,7 @@ of `open-fields`.
              (symbol (or "." "," "+" "-" "*" "/" "^" "(" ")" "=")))
     
           (peg-run (peg stuff)
-                   (lambda (x) (message "failed %s" x))
+                   (lambda (x) (message "Failed %s" x))
                    (lambda (x)
                      (funcall x)
                      `((:fields . ,closed-fields)
@@ -471,8 +484,8 @@ both places to keep them synced.
       "Reduce the field to a number.
     
     Parse the field again, replacing spans with random numbers and
-    evaluating arithmetic operations. The field shouldn't have any
-    internal fields so this should result in a single number. Return
+    evaluating arithmetic operations.  The field shouldn't have any
+    internal fields so this should result in a single number.  Return
     that number."
       (with-peg-rules
           ((field "[" space (or math-func expression sequence assignment 
value) space "]")
@@ -502,7 +515,7 @@ both places to keep them synced.
            (var-lhs (substring letter)) ; var for assignment
            (var-rhs "$" (substring letter) ; var for use
                     `(v -- (let ((val (alist-get (intern v) 
org-mathsheet--var-list)))
-                             (or val (error "var %s not set" v)))))
+                             (or val (error "Var %s not set" v)))))
            (math-func (substring (or "sqrt" "sin" "cos" "tan" "asin" "acos" 
"atan" "floor" "ceil" "round"))
                       parenthetical
                       `(f v -- (string-to-number (calc-eval (format "%s(%s)" f 
v)))))
@@ -511,7 +524,7 @@ both places to keep them synced.
            (digit [0-9]))
     
         (peg-run (peg field)
-                 (lambda (x) (message "failed %s" x))
+                 (lambda (x) (message "Failed %s" x))
                  (lambda (x) (car (funcall x))))))
 
 
@@ -522,10 +535,10 @@ Replace a field with the value returned from reducing it. 
This uses
 the field.
 
     (defun org-mathsheet--replace-field (node)
-      "Replace a field with the number to which it reduces
+      "Replace a field with the number to which it reduces.
     
     Update the current buffer by replacing the field at point in the
-    current buffer with the number it reduces to. NODE contains the
+    current buffer with the number it reduces to.  NODE contains the
     info for the current field."
       (let ((start (caaddr node))
             (end (1+ (cdaddr node)))
@@ -546,21 +559,20 @@ visit the node. We use the last field in the field 
structure to keep
 track of which fields have been visited.
 
     (defun org-mathsheet--dfs-visit (node fields)
-      "Visit NODE as part of a DFS of the problem
+      "Visit NODE as part of a DFS of the problem.
     
     Traverse the fields of a problem using depth first search to
-    ensure that field replacement happens in dependency order. FIELDS
-    is a list of all fields in the problem."
+    ensure that field replacement happens in dependency order.
+    FIELDS is a list of all fields in the problem."
       (pcase (cadddr node)
-        (1 (error "cycle detected")) ; cycle
+        (1 (error "Cycle detected")) ; cycle
         (2)                          ; skip
         (_                           ; process
          (setcar (cdddr node) 1)     ; started
-         (let ((deps (cadr node)))
-           (dolist (dep deps)
-             (org-mathsheet--dfs-visit
-              (assq dep fields)
-              fields)))
+         (dolist (dep (cadr node))
+           (org-mathsheet--dfs-visit
+            (assq dep fields)
+            fields))
          (org-mathsheet--replace-field node) ; visit
          (setcar (cdddr node) 2)))) ; mark done
 
@@ -572,10 +584,10 @@ processes all fields in a problem.
     (full-problem (buffer-substring (point-at-bol) (point-at-eol)))
 
     (defun org-mathsheet--fill-problem (full-problem)
-      "Replace all fields in FULL-PROBLEM
+      "Replace all fields in FULL-PROBLEM.
     
     Goes through all fields in the given problem in dependency order
-    and replaces fields with numbers. When this completes the problem
+    and replaces fields with numbers.  When this completes the problem
     will be ready to solve."
         (with-temp-buffer
           ;; stage problem in temp buffer
@@ -609,10 +621,10 @@ each template sequentially. In order to mix them up we 
shuffle the
 whole set and then reorder by `order`.
 
     (defun org-mathsheet--generate-problems (template-name count)
-      "Use templates from TEMPLATE-NAME to generate COUNT problems
+      "Use templates from TEMPLATE-NAME to generate COUNT problems.
     
     Generate problems and answers based on what is defined in the
-    given template table. The template table defines problem
+    given template table.  The template table defines problem
     templates as well as relative weights and how they should be
     ordered."
       (let (total-weight templates problems)
@@ -628,12 +640,11 @@ whole set and then reorder by `order`.
                            (seq-drop (org-table-to-lisp) 2)))) ; load the 
table, drop the header
     
         ;; sort by weight (low to high)
-        (setq templates (sort templates (lambda (a b) (< (car a) (car b))))
+        (setq templates (sort templates #'car-less-than-car)
               ;; calc total weight
-              total-weight (float
-                            (seq-reduce (lambda (total item) (+ total (car 
item)))
-                                        templates
-                                        0)))
+              total-weight (seq-reduce (lambda (total item) (+ total (car 
item)))
+                                       templates
+                                       0.0))
     
         ;; calculate number for each row
         (dotimes (ii (length templates))
@@ -683,10 +694,10 @@ whole set and then reorder by `order`.
         (dotimes (ii (- (length problems) 1))
           (let ((jj (+ (random (- (length problems) ii)) ii)))
             (cl-psetf (elt problems ii) (elt problems jj)
-                   (elt problems jj) (elt problems ii))))
+                      (elt problems jj) (elt problems ii))))
     
         ;; sort by order
-        (sort problems (lambda (a b) (< (caddr a) (caddr b))))
+        (setq problems (sort problems (lambda (a b) (< (caddr a) (caddr b)))))
     
         ;; return problems and answers, drop header
         problems))
@@ -755,14 +766,48 @@ another.
 
 ### Lay out page
 
-This wraps the problems with a tex header and footer.
+This wraps the problems with a LaTeX header and footer.
 
-This template doesn't use noweb but it uses noweb syntax (`<<label>>`) to
-mark where org-mathsheet will insert content. It's not possible actually
-use noweb here since this template must be tangled to org-mathsheet.el as
-a template.
+This template doesn't use noweb but it uses noweb syntax (`<<label>>`)
+to mark where org-mathsheet will insert content. It's not possible
+actually use noweb here since the problems and answers are coming from
+elisp and generated at runtime. Instead this template must be tangled
+to org-mathsheet.el as a template so the elisp functions can use it.
 
-I found the solution for how to enumerate with circled numbers 
[here](https://latex.org/forum/viewtopic.php?p=40006&sid=d202f756313add2391c3140fbeafe2ff#p40006).
+    \documentclass[12pt]{exam}
+    \usepackage[top=1in, bottom=0.5in, left=0.8in, right=0.8in]{geometry}
+    \usepackage{multicol}
+    \usepackage{rotating}
+    \usepackage{xcolor}
+    
+    \pagestyle{head}
+    
\header{Name:\enspace\makebox[2.2in]{\hrulefill}}{}{Date:\enspace\makebox[2.2in]{\hrulefill}}
+    
+    \begin{document}
+    
+      \noindent <<instruction>>
+    
+      \begin{questions}
+        <<problems>>
+      \end{questions}
+    
+      \vspace*{\fill}
+    
+      \vspace*{0.1cm}
+      \noindent\rule{\linewidth}{0.4pt}
+      \vspace*{0.1cm}
+    
+      \begin{turn}{180}
+        \begin{minipage}{\linewidth}
+          \color{gray}
+          \footnotesize
+          \begin{questions}
+            <<answers>>
+          \end{questions}
+        \end{minipage}
+      \end{turn}
+    
+    \end{document}
 
 
 ### Convert calc to latex
@@ -773,15 +818,15 @@ written to a PDF we convert them to latex. emacs calc 
already knows
 how to convert between formats, so we let it do it.
 
     (defun org-mathsheet--convert-to-latex (expr)
-      "Format the given calc expression EXPR for LaTeX
+      "Format the given calc expression EXPR for LaTeX.
     
-    EXPR should be in normal calc format. The result is the same
+    EXPR should be in normal calc format.  The result is the same
     expression (not simplified) but in LaTeX format."
       (let* ((calc-language 'latex)
              (calc-expr (math-read-expr expr))
              (latex-expr (math-format-stack-value (list calc-expr 1 nil)))
              (latex-expr-cleaned (replace-regexp-in-string (rx "1:" (* space)) 
"" latex-expr)))
-        (concat "$" latex-expr-cleaned "$")))
+        (concat "\\(" latex-expr-cleaned "\\)")))
 
 
 ### Write PDF
@@ -795,8 +840,8 @@ will overwrite the same file.
     (defun org-mathsheet--gen-worksheet (file-name instruction problems 
prob-cols)
       "Generate a worksheet with PROBLEMS.
     
-    Write a file named FILE-NAME. Include the INSTRUCTION line at the
-    top. The problems will be arranged in PROB-COLS columns. The
+    Write a file named FILE-NAME.  Include the INSTRUCTION line at the
+    top.  The problems will be arranged in PROB-COLS columns.  The
     answers will be in 4 columns."
       (with-temp-file (concat file-name ".tex")
         (insert org-mathsheet--worksheet-template)
@@ -830,8 +875,9 @@ will overwrite the same file.
               (insert (format "\\question %s\n"
                               (org-mathsheet--convert-to-latex (cadr row)))))
             (insert "\\end{multicols}\n"))))
-      (shell-command (concat "texi2pdf " file-name ".tex")
-                     (get-buffer-create "*Standard output*")))
+      (call-process
+       "texi2pdf" nil (get-buffer-create "*Standard output*") nil
+       (concat  file-name ".tex")))
 
 
 ### Footer
diff --git a/mathsheet.org b/mathsheet.org
index 4c1440c4ce..02e42d7c4f 100644
--- a/mathsheet.org
+++ b/mathsheet.org
@@ -169,6 +169,10 @@ This is an example problem-set block.
 *** Header
 This is the standard emacs package header.
 
+~emacs 26~ is needed for ~seq-random-elt~.  ~calc~ is used to solve the
+problems as well as converting them to mathematical notation in LaTeX
+format.
+
 #+begin_src elisp :tangle org-mathsheet.el
   ;;; org-mathsheet.el --- Generate dynamic math worksheets  -*- 
lexical-binding:t -*-
 
@@ -178,7 +182,9 @@ This is the standard emacs package header.
   ;; Keywords: tools, education, math
   ;; Homepage: https://gitlab.com/ianxm/org-mathsheet
   ;; Version: 1.0
-  ;; Package-Requires: ((peg "1.0"))
+  ;; Package-Requires: ((peg "1.0")
+  ;;                    (emacs "26.0")
+  ;;                    calc)
 
   ;; This file is not part of GNU Emacs.
 
@@ -206,10 +212,17 @@ This is the standard emacs package header.
 #+end_src
 
 *** Dependencies
-This package needs [[https://elpa.gnu.org/packages/peg.html][peg]].
+This package needs [[https://elpa.gnu.org/packages/peg.html][peg]]. We also 
need [[https://www.gnu.org/software/emacs/manual/html_mono/calc.html][calc]] 
and some [[https://orgmode.org/manual/Tables.html][org-table]] and
+[[https://orgmode.org/org.html#Working-with-Source-Code][org-babel]] functions.
 
 #+begin_src elisp :tangle org-mathsheet.el
   (require 'peg)
+  (require 'calc)
+
+  (declare-function math-read-expr "calc-ext")
+  (declare-function org-table-align "org-table")
+  (declare-function org-table-to-lisp "org-table")
+  (declare-function org-babel-named-data-regexp-for-name "ob-core")
 #+end_src
 
 *** Variables
@@ -483,11 +496,10 @@ track of which fields have been visited.
       (2)                          ; skip
       (_                           ; process
        (setcar (cdddr node) 1)     ; started
-       (let ((deps (cadr node)))
-         (dolist (dep deps)
-           (org-mathsheet--dfs-visit
-            (assq dep fields)
-            fields)))
+       (dolist (dep (cadr node))
+         (org-mathsheet--dfs-visit
+          (assq dep fields)
+          fields))
        (org-mathsheet--replace-field node) ; visit
        (setcar (cdddr node) 2)))) ; mark done
 #+end_src
@@ -600,12 +612,11 @@ whole set and then reorder by ~order~.
                          (seq-drop (org-table-to-lisp) 2)))) ; load the table, 
drop the header
 
       ;; sort by weight (low to high)
-      (setq templates (sort templates (lambda (a b) (< (car a) (car b))))
+      (setq templates (sort templates #'car-less-than-car)
             ;; calc total weight
-            total-weight (float
-                          (seq-reduce (lambda (total item) (+ total (car 
item)))
-                                      templates
-                                      0)))
+            total-weight (seq-reduce (lambda (total item) (+ total (car item)))
+                                     templates
+                                     0.0))
 
       ;; calculate number for each row
       (dotimes (ii (length templates))
@@ -655,10 +666,10 @@ whole set and then reorder by ~order~.
       (dotimes (ii (- (length problems) 1))
         (let ((jj (+ (random (- (length problems) ii)) ii)))
           (cl-psetf (elt problems ii) (elt problems jj)
-                 (elt problems jj) (elt problems ii))))
+                    (elt problems jj) (elt problems ii))))
 
       ;; sort by order
-      (sort problems (lambda (a b) (< (caddr a) (caddr b))))
+      (setq problems (sort problems (lambda (a b) (< (caddr a) (caddr b)))))
 
       ;; return problems and answers, drop header
       problems))
@@ -725,17 +736,16 @@ another.
 
 ** Generate PDF
 *** Lay out page
-This wraps the problems with a tex header and footer.
-
-This template doesn't use noweb but it uses noweb syntax (~<<label>>~) to
-mark where org-mathsheet will insert content. It's not possible actually
-use noweb here since this template must be tangled to org-mathsheet.el as
-a template.
+This wraps the problems with a LaTeX header and footer.
 
-I found the solution for how to enumerate with circled numbers 
[[https://latex.org/forum/viewtopic.php?p=40006&sid=d202f756313add2391c3140fbeafe2ff#p40006][here]].
+This template doesn't use noweb but it uses noweb syntax (~<<label>>~)
+to mark where org-mathsheet will insert content. It's not possible
+actually use noweb here since the problems and answers are coming from
+elisp and generated at runtime. Instead this template must be tangled
+to org-mathsheet.el as a template so the elisp functions can use it.
 
 #+name: page
-#+begin_src latex :results value silent
+#+begin_src latex :exports code :results value silent
   \documentclass[12pt]{exam}
   \usepackage[top=1in, bottom=0.5in, left=0.8in, right=0.8in]{geometry}
   \usepackage{multicol}
@@ -788,7 +798,7 @@ how to convert between formats, so we let it do it.
            (calc-expr (math-read-expr expr))
            (latex-expr (math-format-stack-value (list calc-expr 1 nil)))
            (latex-expr-cleaned (replace-regexp-in-string (rx "1:" (* space)) 
"" latex-expr)))
-      (concat "$" latex-expr-cleaned "$")))
+      (concat "\\(" latex-expr-cleaned "\\)")))
 #+end_src
 *** Write PDF
 This inserts instruction line and generated problems into the page
@@ -836,8 +846,9 @@ will overwrite the same file.
             (insert (format "\\question %s\n"
                             (org-mathsheet--convert-to-latex (cadr row)))))
           (insert "\\end{multicols}\n"))))
-    (shell-command (concat "texi2pdf " file-name ".tex")
-                   (get-buffer-create "*Standard output*")))
+    (call-process
+     "texi2pdf" nil (get-buffer-create "*Standard output*") nil
+     (concat  file-name ".tex")))
 #+end_src
 *** Footer
 #+begin_src elisp :tangle org-mathsheet.el
diff --git a/org-mathsheet.el b/org-mathsheet.el
index f76924c89f..31433a5c6d 100644
--- a/org-mathsheet.el
+++ b/org-mathsheet.el
@@ -6,7 +6,9 @@
 ;; Keywords: tools, education, math
 ;; Homepage: https://gitlab.com/ianxm/org-mathsheet
 ;; Version: 1.0
-;; Package-Requires: ((peg "1.0"))
+;; Package-Requires: ((peg "1.0")
+;;                    (emacs "26.0")
+;;                    calc)
 
 ;; This file is not part of GNU Emacs.
 
@@ -25,14 +27,20 @@
 
 ;;; Commentary:
 
-;; This package generates dynamic math worksheets.  The types and
-;; distribution of problems is highly customizable.  Problem sets are
+;; This package generates dynamic math worksheets. The types and
+;; distribution of problems is highly customizable. Problem sets are
 ;; defined in org tables, generated in dynamic blocks for review, and
 ;; exported to PDF for printing.
 
 ;;; Code:
 
 (require 'peg)
+(require 'calc)
+
+(declare-function math-read-expr "calc-ext")
+(declare-function org-table-align "org-table")
+(declare-function org-table-to-lisp "org-table")
+(declare-function org-babel-named-data-regexp-for-name "ob-core")
 
 (let ((page '"\\documentclass[12pt]{exam}
 \\usepackage[top=1in, bottom=0.5in, left=0.8in, right=0.8in]{geometry}
@@ -210,11 +218,10 @@ FIELDS is a list of all fields in the problem."
     (2)                          ; skip
     (_                           ; process
      (setcar (cdddr node) 1)     ; started
-     (let ((deps (cadr node)))
-       (dolist (dep deps)
-         (org-mathsheet--dfs-visit
-          (assq dep fields)
-          fields)))
+     (dolist (dep (cadr node))
+       (org-mathsheet--dfs-visit
+        (assq dep fields)
+        fields))
      (org-mathsheet--replace-field node) ; visit
      (setcar (cdddr node) 2)))) ; mark done
 
@@ -263,12 +270,11 @@ ordered."
                        (seq-drop (org-table-to-lisp) 2)))) ; load the table, 
drop the header
 
     ;; sort by weight (low to high)
-    (setq templates (sort templates (lambda (a b) (< (car a) (car b))))
+    (setq templates (sort templates #'car-less-than-car)
           ;; calc total weight
-          total-weight (float
-                        (seq-reduce (lambda (total item) (+ total (car item)))
-                                    templates
-                                    0)))
+          total-weight (seq-reduce (lambda (total item) (+ total (car item)))
+                                   templates
+                                   0.0))
 
     ;; calculate number for each row
     (dotimes (ii (length templates))
@@ -318,10 +324,10 @@ ordered."
     (dotimes (ii (- (length problems) 1))
       (let ((jj (+ (random (- (length problems) ii)) ii)))
         (cl-psetf (elt problems ii) (elt problems jj)
-               (elt problems jj) (elt problems ii))))
+                  (elt problems jj) (elt problems ii))))
 
     ;; sort by order
-    (sort problems (lambda (a b) (< (caddr a) (caddr b))))
+    (setq problems (sort problems (lambda (a b) (< (caddr a) (caddr b)))))
 
     ;; return problems and answers, drop header
     problems))
@@ -375,7 +381,7 @@ expression (not simplified) but in LaTeX format."
          (calc-expr (math-read-expr expr))
          (latex-expr (math-format-stack-value (list calc-expr 1 nil)))
          (latex-expr-cleaned (replace-regexp-in-string (rx "1:" (* space)) "" 
latex-expr)))
-    (concat "$" latex-expr-cleaned "$")))
+    (concat "\\(" latex-expr-cleaned "\\)")))
 
 (defun org-mathsheet--gen-worksheet (file-name instruction problems prob-cols)
   "Generate a worksheet with PROBLEMS.
@@ -415,8 +421,9 @@ answers will be in 4 columns."
           (insert (format "\\question %s\n"
                           (org-mathsheet--convert-to-latex (cadr row)))))
         (insert "\\end{multicols}\n"))))
-  (shell-command (concat "texi2pdf " file-name ".tex")
-                 (get-buffer-create "*Standard output*")))
+  (call-process
+   "texi2pdf" nil (get-buffer-create "*Standard output*") nil
+   (concat  file-name ".tex")))
 
 (provide 'org-mathsheet)
 

Reply via email to