Making an Exercise and Solution Environment
Table of Contents
1. The Problem
In a number of my org mode files I include exercise with hints and solutions.
In the pdf files, which I make via \(\LaTeX\), I use the answers
package print the exercises in the main text and the hints and solutions at the end of the file.
However, when exporting the file to html
via my website I would like to see the exercises, hints and solutions printed like so:
How to make an excercise environment like this?
Hint
And a hint like this?
Solution
To see how to do this, read on.
2. My Environment
I use emacs
and org mode
to write files, and nikola
to make my homepage.
After some searching on the web, I found the org-special-block-extras
package which proved promissing for my goal.
The related github page shows how to install it.
Here are the steps I followed.
First I installed the org mode
plugin for nikola
. The orgmode.py
file shows that the next command can used to convert an org
to an html
file that nikola
can read.
/usr/bin/emacs --batch -l init.el --eval '(nikola-html-export "input.org" "output.html")'
The org mode plugin also provides an init.el
file that I used as a starting point.
The init.el
file contains the line (setq package-load-list '((htmlize t)))
. This should be removed. Why?
Hint
What does package-load-list
do?
Solution
I need to install org-special-block-extras
, and this command prevents that.
3. The adapted init.el
Since I need functionality from org-special-block-extras
, I have to update the init.el
anyway, so here it is.
3.1. Loading the relevant packages
Since I use straight
and use-package
, I need to put this on the top of the init.el
file. I copied this straight from my regular init.el
(defvar bootstrap-version)
(let ((bootstrap-file
(expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
(bootstrap-version 5))
(unless (file-exists-p bootstrap-file)
(with-current-buffer
(url-retrieve-synchronously
"https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
'silent 'inhibit-cookies)
(goto-char (point-max))
(eval-print-last-sexp)))
(load bootstrap-file nil 'nomessage))
(setq package-enable-at-startup nil)
(straight-use-package 'use-package)
(setq straight-use-package-by-default t)
Next I load two packages. The hook on org-mode
is necessary.
(use-package htmlize
:ensure t)
(use-package org-special-block-extras
:ensure t
;; All relevant Lisp functions are prefixed ‘o-’; e.g., `o-docs-insert'.
:hook (org-mode . org-special-block-extras-mode)
:custom
(o-docs-libraries
'("~/org-special-block-extras/documentation.org")
"The places where I keep my ‘#+documentation’"))
(require 'org)
(require 'ox-html)
(require 'org-special-block-extras)
3.2. Updating the code for box
and details
blocks
I adapted the fontsizes and the padding of the details
block of org-special-block-extras
.
I just copied the relevant part of org-special-block-extras.el
, removed the documentation string to keep this file small, and changed the padding and font size.
(org-defblock details (title "Details"
background-color "#e5f5e5" title-color "green")
(pcase backend
(`latex (concat (pcase (substring background-color 0 1)
("#" (format "\\definecolor{osbe-bg}{HTML}{%s}" (substring background-color 1)))
(_ (format "\\colorlet{osbe-bg}{%s}" background-color)))
(pcase (substring title-color 0 1)
("#" (format "\\definecolor{osbe-fg}{HTML}{%s}" (substring title-color 1)))
(_ (format "\\colorlet{osbe-fg}{%s}" title-color)))
(format "\\begin{quote}
\\begin{tcolorbox}[colback=osbe-bg,colframe=osbe-fg,title={%s},sharp corners,boxrule=0.4pt]
%s
\\end{tcolorbox}
\\end{quote}" title contents)))
(_ (format "<details class=\"code-details\"
style =\"padding: 0.6em;
background-color: %s;
border-radius: 15px;
color: hsl(157 75% 20%);
font-size: 1em;
box-shadow: 0.05em 0.1em 5px 0.01em #00000057;\">
<summary>
<strong>
<font face=\"Courier\" size=\"3\" color=\"%s\">
%s
</font>
</strong>
</summary>
%s
</details>" background-color title-color title contents))))
And also for the box
block.
(org-defblock box (title "" background-color nil shadow nil)
(pcase backend
(`latex
(apply #'concat
`("\\begin{tcolorbox}[title={" ,title "}"
",colback=" ,(pp-to-string (or background-color 'red!5!white))
",colframe=red!75!black, colbacktitle=yellow!50!red"
",coltitle=red!25!black, fonttitle=\\bfseries,"
"subtitle style={boxrule=0.4pt, colback=yellow!50!red!25!white}]"
,contents
"\\end{tcolorbox}")))
;; CSS syntax: “box-shadow: specification, specification, ...”
;; where a specification is of the shape “[inset] x_offset y_offset [blur [spread]] color”.
(_ (-let [haze (lambda (left right deep-right deep-left)
(format "width: 50%%; margin: auto; box-shadow: %s"
(thread-last (list (cons right "8px 6px 13px 8px %s")
(cons left "-16px 12px 20px 16px %s")
(cons deep-right "48px 36px 71px 28px %s")
(cons deep-left "-48px -20px 71px 28px %s"))
(--filter (car it))
(--map (format (cdr it) (car it)))
(s-join ","))))]
(format "<div style=\"%s\"> <h3>%s</h3> %s </div>"
(s-join ";" `( "padding: 0.5em;"
,(format "background-color: %s" (org-subtle-colors (format "%s" (or background-color "green"))))
"border-radius: 15px"
"font-size: 1em"
,(when shadow
(cond
((equal shadow t)
(funcall haze "hsl(60, 100%, 50%)" "hsl(1, 100%, 50%)" "hsl(180, 100%, 50%)" nil))
((equal shadow 'inset)
(funcall haze "inset hsl(60, 100%, 50%)" "inset hsl(1, 100%, 50%)" "inset hsl(180, 100%, 50%)" nil))
((or (stringp shadow) (symbolp shadow))
(format "box-shadow: 10px 10px 20px 0px %s; width: 50%%; margin: auto" (pp-to-string shadow)))
((json-plist-p shadow)
(-let [(&plist :left X :right Y :deep-right Z :deep-left W) shadow]
(funcall haze X Y Z W)))
(:otherwise (-let [(X Y Z W) shadow]
(funcall haze X Y Z W)))))))
title contents)))))
3.3. Making the exercise, hint and solution blocks
The exercise, hint and solution blocks are now simple to implement (after I read the examples on org-special-blocks-extra
).
(org-defblock exercise (title nil)
(org-thread-blockcall raw-contents
(box )))
(org-defblock solution (title "Solution")
(org-thread-blockcall raw-contents
(details title :title-color "red")))
(org-defblock hint (title "Hint")
(org-thread-blockcall raw-contents
(details title))) ; :title-color "red")))
I copied the above on top of the init.el
file, and kept the rest.
4. Testing
Let's test this on this file. As this file sits in the posts
we need to include the path to init.el
.
/usr/bin/emacs --batch -l ../plugins/orgmode/init.el --eval '(nikola-html-export "making-an-exercise-environment.org" "../output/output.html")'