Org Mode tables and fill-in quizzes – Latin verb conjugation drills in Emacs
| emacs, orgI was looking for a Latin verb conjugation drill similar to these ones for and nouns and pronouns. I liked the instant feedback and the ability to quickly get hints. I couldn’t find an online drill I liked, though, so I made my own with Emacs and Org. (Because… why not?)
I wrote some code that would take a table like this:
present – 1st sing. – ago / agere | agO |
present – 2nd sing. – ago / agere | agis |
present – 3rd sing. – ago / agere | agit |
present – 1st plu. – ago / agere | agimus |
present – 2nd plu. – ago / agere | agitis |
present – 3rd plu. – ago / agere | agunt |
imperfect – 1st sing. – ago / agere | agEbam |
imperfect – 2nd sing. – ago / agere | agEbAs |
imperfect – 3rd sing. – ago / agere | agEbat |
imperfect – 1st plu. – ago / agere | agEbAmus |
imperfect – 2nd plu. – ago / agere | agEbAtis |
imperfect – 3rd plu. – ago / agere | agEbant |
future – 1st sing. – ago / agere | agam |
future – 2nd sing. – ago / agere | agEs |
future – 3rd sing. – ago / agere | agEt |
future – 1st plu. – ago / agere | agEmus |
future – 2nd plu. – ago / agere | agEtis |
future – 3rd plu. – ago / agere | agent |
I can call my/make-fill-in-quiz
to get a quiz buffer that looks like this. If I get stuck, ?
shows me a hint in the echo area.
To make it easier, I’ve left case-fold-search
set to nil
so that I don’t have to match the case (uppercase vowels = macrons), but I can set case-fold-search
to t
if I want to make sure I’ve got the macrons in the right places.
Here’s the code to display the quiz buffer.
(require 'widget) (defun my/check-widget-value (widget &rest ignore) "Provide visual feedback for WIDGET." (cond ((string= (widget-value widget) "?") ;; Asking for hint (message "%s" (widget-get widget :correct)) (widget-value-set widget "")) ;; Use string-match to obey case-fold-search ((string-match (concat "^" (regexp-quote (widget-get widget :correct)) "$") (widget-value widget)) (message "Correct") (goto-char (widget-field-start widget)) (goto-char (line-end-position)) (insert "✓") (widget-forward 1) ))) (defun my/make-fill-in-quiz (&optional quiz-table) "Create an fill-in quiz for the Org table at point. The Org table's first column should have the questions and the second column should have the answers." (interactive (list (org-babel-read-table))) (with-current-buffer (get-buffer-create "*Quiz*") (kill-all-local-variables) (let ((inhibit-read-only t)) (erase-buffer)) (remove-overlays) (mapc (lambda (row) (widget-insert (car row)) (widget-insert "\t") (widget-create 'editable-field :size 15 :correct (cadr row) :notify 'my/check-widget-value) (widget-insert "\n")) quiz-table) (widget-create 'push-button :table quiz-table :notify (lambda (widget &rest ignore) (my/make-fill-in-quiz (widget-get widget :table))) "Reset") (use-local-map widget-keymap) (widget-setup) (goto-char (point-min)) (widget-forward 1) (switch-to-buffer (current-buffer))))
Incidentally, I generated the table above from a larger table of Latin verb conjugations in the appendix of Wheelock’s Latin, specified like this:
| laudO | moneO | agO | audiO | capiO | | laudAs | monEs | agis | audIs | capis | | laudat | monet | agit | audit | capit | | laudAmus | monEmus | agimus | audImus | capimus | | laudAtis | monEtis | agitis | audItis | capitis | | laudant | monent | agunt | audiunt | capiunt | | laudAbam | monEbam | agEbam | audiEbam | capiEbam | | laudAbas | monEbas | agEbAs | audiEbAs | capiEbas | | laudAbat | monEbat | agEbat | audiEbat | capiEbat | | laudAbAmus | monEbAmus | agEbAmus | audiEbAmus | capiEbAmus | | laudAbAtis | monEbAtis | agEbAtis | audiEbAtis | capiEbAtis | | laudAbant | monEbant | agEbant | audiEbant | capiEbant | | laudAbO | monEbO | agam | audiam | capiam | | laudAbis | monEbis | agEs | audiEs | capiEs | | laudAbit | monEbit | agEt | audiet | capiet | | laudAbimus | monEbimus | agEmus | audiEmus | capiEmus | | laudAbitis | monEbitis | agEtis | audiEtis | capiEtis | | laudAbunt | monEbunt | agent | audient | capient |
with the code:
#+begin_src emacs-lisp :var present=present-indicative-active :var imperfect=imperfect-indicative-active :var future=future-indicative-active (defun my/label-latin-with-verbs (table verbs persons tense) (apply 'append (-zip-with (lambda (row person) (-zip-with (lambda (word verb) (list word (format "%s - %s - %s" tense person verb))) row verbs)) table (-cycle persons)))) (apply 'append (mapcar (lambda (tense) (my/label-latin-with-verbs (symbol-value tense) '("laudo / laudare" "moneo / monEre" "ago / agere" "audiO / audIre" "capiO / capere") '("1st sing." "2nd sing." "3rd sing." "1st plu." "2nd plu." "3rd plu.") (symbol-name tense))) '(present imperfect future))) #+end_src
This uses dash.el
for the -zip-with
and -cycle
functions. There’s probably a much better way to process the lists, but I’m still getting the hang of thinking properly functionally… =)
Anyway, I’m sure it will be handy for a number of other quiz-like things. org-drill and org-drill-table will probably come in handy for flashcards, too!