diff --git a/.gitignore b/.gitignore index 6f9c28d..a4e71ae 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ /ltximg/ **/.DS_Store +/tests/indent.log +/indent.log diff --git a/org-special-block-extras.el b/org-special-block-extras.el index d283fae..9101efa 100644 --- a/org-special-block-extras.el +++ b/org-special-block-extras.el @@ -579,7 +579,7 @@ Example declaration, with all possible features shown: (org-defblock remark (editor \"Editor Remark\" :face angry-red) (color \"red\" signoff \"\") - \"Top level (HTML & LaTeX)O-RESPECT-NEWLINES? editorial remarks; in Emacs they're angry red.\" + \"Top level (HTML & LaTeX) editorial remarks; in Emacs they're angry red.\" (format (if (equal backend 'html) \"⟦%s: %s%s⟧\" \"{\\color{%s}\\bfseries %s: %s%s}\") @@ -1791,7 +1791,7 @@ this command gives a searchable way to insert doc links." 'variable-documentation)) (-let [it (shell-command-to-string (format "wn %s -over -synsn" label))] - (if (s-blank-p it) + (if (and (s-blank-p it) (not org-export-with-broken-links)) (error "Error: No documentation-glossary entry for “%s”!" label) it))))) @@ -2184,6 +2184,15 @@ what is required by MathJaX." (s-join "\\\\") (format "$$\\begin{align*} & %s \n\\end{align*}$$"))) +(setq my/theorem-counter 0) + +(org-defblock theorem (title) + "Show block contents prefixed with “Theorem 𝒏”, where the 𝒏umbering automatically increments." + (format "
Theorem %s%s. %s
" + (cl-incf my/theorem-counter) + (if title (format " [“%s”]" title) "") + (org-parse raw-contents))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (provide 'org-special-block-extras) diff --git a/org-special-block-extras.org b/org-special-block-extras.org index 78790a9..a55f260 100644 --- a/org-special-block-extras.org +++ b/org-special-block-extras.org @@ -1759,7 +1759,7 @@ Example declaration, with all possible features shown: (org-defblock remark (editor \"Editor Remark\" :face angry-red) (color \"red\" signoff \"\") - \"Top level (HTML & LaTeX)O-RESPECT-NEWLINES? editorial remarks; in Emacs they're angry red.\" + \"Top level (HTML & LaTeX) editorial remarks; in Emacs they're angry red.\" (format (if (equal backend 'html) \"⟦%s: %s%s⟧\" \"{\\color{%s}\\bfseries %s: %s%s}\") @@ -2427,6 +2427,7 @@ We will make use of this block below when we get to guided problems ;-) :CUSTOM_ID: Longer_Example_Demonstrating_Org-markup_with_org-demo :END: + Sometimes, we want to show verbatim source and its resulting rendition ---which is a major part of this article! So, let's make a block to mitigate such an error-prone tedium. @@ -7034,7 +7035,7 @@ doc:org-docs-load-libraries 'variable-documentation)) (-let [it (shell-command-to-string (format "wn %s -over -synsn" label))] - (if (s-blank-p it) + (if (and (s-blank-p it) (not org-export-with-broken-links)) (error "Error: No documentation-glossary entry for “%s”!" label) it))))) diff --git a/tests.el b/tests.el index 2f5c28c..eff103f 100644 --- a/tests.el +++ b/tests.el @@ -147,7 +147,7 @@ a given matching pattern. Such arrows are popular in Term Rewriting Systems." (should (org-html-export-to-html))) -;; [[file:org-special-block-extras.org::*Define links as you define functions: doc:org-deflink][Define links as you define functions: doc:org-deflink:4]] +;; [[file:org-special-block-extras.org::#NEW-org-deflink][Define links as you define functions: doc:org-deflink:4]] (org-deflink shout "Capitalise the link description, if any, otherwise capitalise the label. @@ -235,7 +235,7 @@ post") (* anything) "

\npost")) -;; [[file:org-special-block-extras.org::*Folded Details ---As well as boxed text and subtle colours][Folded Details ---As well as boxed text and subtle colours:4]] +;; [[file:org-special-block-extras.org::#Folded-Details][Folded Details ---As well as boxed text and subtle colours:3]] (deftest "The result is a

tag containing the user's title & text." [details] (⇝ (⟰ "#+begin_details TITLE-RIGHT-HERE @@ -248,7 +248,7 @@ post") "My aside" (* anything) "
")) -;; Folded Details ---As well as boxed text and subtle colours:4 ends here +;; Folded Details ---As well as boxed text and subtle colours:3 ends here (deftest "We have an HTML box enclosing the user's title (in ")) ;; Colours:4 ends here -;; [[file:org-special-block-extras.org::*Nice Keystroke Renditions: kbd:C-h_h][Nice Keystroke Renditions: kbd:C-h_h:3]] +;; [[file:org-special-block-extras.org::#kbd:nice-keystroke-renditions][Nice Keystroke Renditions: kbd:C-h_h:3]] (deftest "It becomes tags, but final symbol non-ascii *may* be ignored" [kbd direct-org-links] (⇝ (⟰ "kbd:C-u_80_-∀") "

\nC-u 80_-∀

")) @@ -421,7 +421,7 @@ post") "M-s h ."))) ;; Nice Keystroke Renditions: kbd:C-h_h:3 ends here -;; [[file:org-special-block-extras.org::*  /“Link Here!”/ & OctoIcons][  /“Link Here!”/ & OctoIcons:3]] +;; [[file:org-special-block-extras.org::#Link-Here-OctoIcons][  /“Link Here!”/ & OctoIcons:3]] (deftest "It works as expected: We have an anchor with the given ID, and the default SVG chain icon." [link:here] (⇝ (⟰ "link-here:example-location (Click the icon and see the URL has changed!)") @@ -431,7 +431,7 @@ post") (* anything))) ;;   /“Link Here!”/ & OctoIcons:3 ends here -;; [[file:org-special-block-extras.org::*Badge Links][Badge Links:2]] +;; [[file:org-special-block-extras.org::#Badge-Links][Badge Links:2]] (deftest "It works when all 5 arguments are provided" [badge] (⇝ (⟰ "badge:Let_me_google_that|for_you!|orange|https://lmgtfy.app/?q=badge+shields.io&iie=1|Elixir") @@ -491,7 +491,7 @@ post") "")) ;; Badge Links:2 ends here -;; [[file:org-special-block-extras.org::*Intro, motivating examples][Intro, motivating examples:4]] +;; [[file:org-special-block-extras.org::#COMMENT-Intro][Intro, motivating examples:4]] (deftest "It gives a tooltip whose title is the Lisp docs of APPLY" [doc] (⇝ (⟰ "doc:apply") @@ -530,7 +530,7 @@ post") "\">Existential Angst.

")) ;; Intro, motivating examples:4 ends here -;; [[file:org-special-block-extras.org::*Marginal, “one-off”, remarks][Marginal, “one-off”, remarks:2]] +;; [[file:org-special-block-extras.org::#Marginal-one-off-remarks][Marginal, “one-off”, remarks:2]] (setq margin (⟰ "/Allah[[margin:][The God of Abraham; known as Elohim in the Bible]] does not burden a soul beyond what it can bear./ --- Quran 2:286")) @@ -553,7 +553,7 @@ post") (⇝ margin "")) ;; Marginal, “one-off”, remarks:2 ends here -;; [[file:org-special-block-extras.org::*Equational Proofs][Equational Proofs:4]] +;; [[file:org-special-block-extras.org::#Equational-Proofs][Equational Proofs:4]] (setq calc (⟰ "#+begin_calc :hint-format \"\\\\left\\{ %s\\\\right.\" + x + y -- Explanation of why $x \\;=\\; y$ diff --git a/tests/badge.yaml b/tests/badge.yaml new file mode 100644 index 0000000..b47fa6e --- /dev/null +++ b/tests/badge.yaml @@ -0,0 +1,190 @@ +input: |- + + 1. It works when all 5 arguments are provided + + badge:Let_me_google_that|for_you!|orange|https://lmgtfy.app/?q=badge+shields.io&iie=1|Elixir + + 2. It works when we use [ [ link ] ] syntax with generous spaces and newlines + + [[badge: Let me google that | for you! | orange | + https://lmgtfy.app/?q=badge+shields.io&iie=1|Elixir]] + + 3. It works when only the first 2 arguments are provided; + asterisks are passed unaltered into the first argument + + badge:Let_me_*not*_google_that|for_you + + 4. It works when all 5 arguments are provided - URL ‘here’ makes it a local link + + badge:key|value|informational|here|Elixir + + 5. We can use spaces, commas, dashes, and percentage symbols in the first argument + + badge:example_with_spaces,_-,_and_%|points_right_here|orange|here + + 6. It works when only first 2 arguments are given: Default colour & logo are green & no logo shown + + badge:key|value + + 7. When only a key is provided, the value slot is shown as an empty green stub + + badge:key + + 8. When only a value is provided, only the value is shown in a default green + ---no stub for the missing key, yay + + badge:|value + + 9. It's only a green stub when provided with an empty key and empty value + + badge:||green + + 10. It's only a green stub when we use no options at all + + [[badge:]] + +expectations: + html: |- +
    +
  1. +

    It works when all 5 arguments are provided

    + +

    + +

    +
  2. + +
  3. +

    + It works when we use [ [ link ] ] syntax with generous spaces and newlines +

    + +

    + +

    +
  4. + +
  5. +

    + It works when only the first 2 arguments are provided; asterisks are + passed unaltered into the first argument +

    + +

    + +

    +
  6. + +
  7. +

    + It works when all 5 arguments are provided - URL ‘here’ makes it a local + link +

    + +

    + +

    +
  8. + +
  9. +

    + We can use spaces, commas, dashes, and percentage symbols in the first + argument +

    + +

    + +

    +
  10. + +
  11. +

    + It works when only first 2 arguments are given: Default colour & logo + are green & no logo shown +

    + +

    + +

    +
  12. + +
  13. +

    + When only a key is provided, the value slot is shown as an empty green + stub +

    + +

    + +

    +
  14. + +
  15. +

    + When only a value is provided, only the value is shown in a default green + —no stub for the missing key, yay +

    + +

    + +

    +
  16. + +
  17. +

    + It's only a green stub when provided with an empty key and empty value +

    + +

    + +

    +
  18. + +
  19. +

    It's only a green stub when we use no options at all

    + +

    + +

    +
  20. +
+ latex: |- + \begin{enumerate} + \item It works when all 5 arguments are provided + + \item It works when we use [ [ link ] ] syntax with generous spaces and newlines + + \item It works when only the first 2 arguments are provided; + asterisks are passed unaltered into the first argument + + \item It works when all 5 arguments are provided - URL ‘here’ makes it a local link + + \item We can use spaces, commas, dashes, and percentage symbols in the first argument + + \item It works when only first 2 arguments are given: Default colour \& logo are green \& no logo shown + + \item When only a key is provided, the value slot is shown as an empty green stub + + \item When only a value is provided, only the value is shown in a default green + ---no stub for the missing key, yay + + \item It's only a green stub when provided with an empty key and empty value + + \item It's only a green stub when we use no options at all + \end{enumerate} diff --git a/tests/box.yaml b/tests/box.yaml new file mode 100644 index 0000000..d2e453e --- /dev/null +++ b/tests/box.yaml @@ -0,0 +1,28 @@ +input: |- + #+begin_box Pay Attention! + This is the key insight... + (We have an HTML box enclosing the user's title and text) + #+end_box + +expectations: + html: |- +
+

Pay Attention!

+

+ This is the key insight… (We have an HTML box enclosing the user's + title and text) +

+
+ latex: |- + \begin{tcolorbox}[title={Pay Attention!},colback=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}] + This is the key insight\ldots{} + (We have an HTML box enclosing the user's title and text) + + \end{tcolorbox} diff --git a/tests/calc.yaml b/tests/calc.yaml new file mode 100644 index 0000000..072d5a8 --- /dev/null +++ b/tests/calc.yaml @@ -0,0 +1,57 @@ +input: |- + + #+begin_calc :hint-format "\\left\{ %s\\right." + + x + + y -- Explanation of why $x \;=\; y$ + Actually, let me explain: + * x + * x′ -- hint 1 + * y -- hint 2 + + No words can appear (in the export) *after* a nested calculation, for now. + + [≤] z + -- + Explanation of why $y \;\leq\; z$ + + -- explain it more, this is ignored from export ;-) + #+end_calc +no-prettier: "t" +expectations: + html: |- + $$\begin{align*} & \begin{split}x\end{split} + \\ + = \;\; & \qquad \color{maroon}{\left{ {\large\substack{\text{ Explanation of why $x \;=\; y$ } \hfill\\ + \text{ Actually, let me explain: } \hfill\\ \begin{split} & \begin{split}x\end{split} + \\ + = \;\; & \qquad \color{maroon}{\left{ {\large\substack{\text{ hint 1 } \hfill\\ + }}\right.} + \\ & \begin{split}x′ \end{split}\\ + = \;\; & \qquad \color{maroon}{\left{ {\large\substack{\text{ hint 2 } \hfill\\ + }}\right.} + \\ & \begin{split}y \end{split} + \end{split}\hfill\\ + }}\right.} + \\ & \begin{split} y \end{split}\\ + ≤ \;\; & \qquad \color{maroon}{\left{ {\large\substack{\text{ Explanation of why $y \;\leq\; z$ } \hfill\\ + }}\right.} + \\ & \begin{split} z\end{split} + \end{align*}$$ + latex: |- + $$\begin{align*} & \begin{split}x\end{split} + \\ + = \;\; & \qquad \color{maroon}{\left{ {\large\substack{\text{ Explanation of why $x \;=\; y$ } \hfill\\ + \text{ Actually, let me explain: } \hfill\\ \begin{split} & \begin{split}x\end{split} + \\ + = \;\; & \qquad \color{maroon}{\left{ {\large\substack{\text{ hint 1 } \hfill\\ + }}\right.} + \\ & \begin{split}x′ \end{split}\\ + = \;\; & \qquad \color{maroon}{\left{ {\large\substack{\text{ hint 2 } \hfill\\ + }}\right.} + \\ & \begin{split}y \end{split} + \end{split}\hfill\\ + }}\right.} + \\ & \begin{split} y \end{split}\\ + ≤ \;\; & \qquad \color{maroon}{\left{ {\large\substack{\text{ Explanation of why $y \;\leq\; z$ } \hfill\\ + }}\right.} + \\ & \begin{split} z\end{split} + \end{align*}$$ diff --git a/tests/colors.yaml b/tests/colors.yaml new file mode 100644 index 0000000..603d939 --- /dev/null +++ b/tests/colors.yaml @@ -0,0 +1,56 @@ +input: |- + + #+begin_red + Look at my text, it's red! + #+end_red + + #+begin_pink + Look at my text, it's pink! + #+end_pink + + #+begin_color cyan + Look at my text, it's cyan! + #+end_color + + green:Look + blue:Here + orange:Buddo! + +expectations: + html: |- + +

Look at my text, it's red!

+
+ + +

Look at my text, it's pink!

+
+ + +

Look at my text, it's cyan!

+
+ +

+ Look + Here + Buddo! +

+ latex: |- + \begingroup\color{red} + Look at my text, it's red! + + \endgroup\, + + \begingroup\color{pink} + Look at my text, it's pink! + + \endgroup\, + + \begingroup\color{cyan} + Look at my text, it's cyan! + + \endgroup\, + + \begingroup\color{green}Look\endgroup\, + \begingroup\color{blue}Here\endgroup\, + \begingroup\color{orange}Buddo\endgroup\,! diff --git a/tests/details.yaml b/tests/details.yaml new file mode 100644 index 0000000..0f9830a --- /dev/null +++ b/tests/details.yaml @@ -0,0 +1,48 @@ +input: |- + + #+begin_details TITLE-RIGHT-HERE :title-color "cyan" + The result is a
tag containing the user's title & text. + But, in LaTeX, this is a colourful box. + #+end_details + + (FIXME: The use of quotes in the :title-color keyword is only required for the LaTeX backend.) + +expectations: + html: |- +
+ + + TITLE-RIGHT-HERE + + +

+ The result is a <details> tag containing the user's title & text. + But, in LaTeX, this is a colourful box. +

+
+ +

+ (FIXME: The use of quotes in the :title-color keyword is only required for the + LaTeX backend.) +

+ latex: |- + \definecolor{osbe-bg}{HTML}{e5f5e5}\colorlet{osbe-fg}{cyan}\begin{quote} + \begin{tcolorbox}[colback=osbe-bg,colframe=osbe-fg,title={TITLE-RIGHT-HERE},sharp corners,boxrule=0.4pt] + The result is a
tag containing the user's title \& text. + But, in \LaTeX{}, this is a colourful box. + + + \end{tcolorbox} + \end{quote} + + (FIXME: The use of quotes in the :title-color keyword is only required for the \LaTeX{} backend.) diff --git a/tests/doc.yaml b/tests/doc.yaml new file mode 100644 index 0000000..4fbe278 --- /dev/null +++ b/tests/doc.yaml @@ -0,0 +1,106 @@ +bug: |- + + Due to eager evaluation of glossary lookups, I need the following + + (setq org-export-with-broken-links t) + +input: |- + The ~doc~ link type can be used to show tooltips for Emacs Lisp commands such as + + doc:thread-last + + However, it can also be used for your own definitions/glossary. + First define a term as follows, which does not render anything in the resulting HTML. + + #+begin_documentation Category Theory :label cat + A theory of typed composition; e.g., typed monoids. + #+end_documentation + + Then you can refer to it using the given :label, if any: + + doc:cat + + Or else refer to it with underscores in lieu of spaces: + + doc:Category_Theory + + Either approach renders the name of the term, not the way its referenced. + If you want to change what's rendered, provide a description: + + [[doc:cat][abstract nonsense]] + + Finally, if ~doc~ gets a label that is not an Emacs Lisp command, variable, nor a user-defined + definition via #+begin_documentation, then we try to look up its definition via the dictionary: + + doc:happiness + +expectations: + html: |- +

+ The doc link type can be used to show tooltips for Emacs Lisp + commands such as +

+ +

+ thread-last +

+ +

+ However, it can also be used for your own definitions/glossary. First define a + term as follows, which does not render anything in the resulting HTML. +

+ +

Then you can refer to it using the given :label, if any:

+ +

+ Category Theory +

+ +

Or else refer to it with underscores in lieu of spaces:

+ +

+ Category Theory +

+ +

+ Either approach renders the name of the term, not the way its referenced. If + you want to change what's rendered, provide a description: +

+ +

+ abstract nonsense +

+ +

+ Finally, if doc gets a label that is not an Emacs Lisp command, + variable, nor a user-defined definition via #+begindocumentation, + then we try to look up its definition via the dictionary: +

+ +

+ happiness +

+ latex: |- + 🚫 The LaTex backend is intentionally unmaintained. + 🫠 Whoops, there seems to be an error: + (void-variable label) diff --git a/tests/e2e.el b/tests/e2e.el new file mode 100644 index 0000000..d5ae522 --- /dev/null +++ b/tests/e2e.el @@ -0,0 +1,220 @@ +;; Emacs Lisp End-to-End Testing +;; +;; Read a YAML file that defines `input' and `expectations'. +;; Run `compute-values' on `input' and check the result is the same as `expectations' +;; Recursively read all YAML files in a specified directory. +;; +;; Sequence: +;; 1. Write a YAML test with no `expectations' +;; 2. (e2e-update-this-test) ;; Or: (e2e-update-tests) +;; 3. (e2e-run-tests) +;; 4. M-x magit, to see how the tests changed, commit if happy. +;; 5. Optionally, (ert-delete-all-tests) +;; +;; +;; Why? +;; +;; I think some of the unit tests I've written capture important +;; facts of the blocks I've defined, however the downside of +;; writing them is that they require thought. +;; +;; For example, nearly all of my tests are against the HTML +;; backend, even though org-special-block-extras is about +;; supporting multiple backends. Moreover, escaped strings aren't pretty. +;; Finally, since writing unit tests has friction, I don't even test +;; all options/keywords of my blocks. +;; +;; As such, I'd like to expedite the test writing process, and e2e.el +;; allows me to capture a snapshot of what a block produces and +;; I can then decide if a future change impacts the snapshot in +;; an acceptable fashion or not. +;; +;; When tests are easy to write, I'm more likely to write more of them. + + +(use-package yaml) +(use-package yaml-mode) +(use-package format-all) +(use-package ert) + + + +(defun e2e--read-yaml-file (file) + "Read and parse a YAML FILE, returning its contents as a hash table." + (with-temp-buffer + (insert-file-contents file) + (yaml-parse-string (buffer-string)))) + + +(defun e2e-run-this-test () + (interactive) + (-let [file (f-relative (buffer-file-name))] + (e2e--make-ert-test-for-yaml-file file) + (ert (concat "e2e/" (file-name-base file))))) + +(defun e2e--make-ert-test-for-yaml-file (file) + "ERT-compatible test for a YAML FILE based on 'input' and 'expectations'." + ;; Create a separate ert test for each YAML file + (let* ((data (e2e--read-yaml-file file)) + (input (gethash 'input data)) + (no-prettier (gethash 'no-prettier data)) + (expected-results (gethash 'expectations data)) + (actual-results (compute-values input no-prettier))) + + (eval `(ert-deftest ,(intern (format "e2e/%s" (file-name-base file))) () + :expected-result ,(if (map-elt data 'fails) :failed :passed) + ;; Iterate over each key in expected-results and create assertions + (map-every-p (lambda (key expected-value) + (let ((actual-value (map-elt ,actual-results key 'not-found))) + + (ert-info (`(lambda () + (when ,(and (stringp actual-value) (stringp expected-value)) + (insert-button "Show Diff" + 'action (lambda (button) + (e2e--show-string-diff ,expected-value ,actual-value)) + 'follow-link t + 'help-echo "Click to see the diff.")) + (format " Test failed in file “%s” for key “%s”" ,,file (quote ,key)))) + (should (equal actual-value expected-value))))) + ,expected-results) + + ;; TODO: Also assert that we can create a standalone PDF, unless there's a latex-backend-not-maintained key. + (when nil ;; unless (map-elt data 'latex-backend-not-maintained) + (let* ((file.tex (concat (f-base file) ".tex")) + (required-latex-imports (map-elt data 'required-latex-imports)) + (latex (map-elt (map-elt data 'expectations) 'latex))) + (with-temp-file file.tex + (insert + (format "\\documentclass{standalone} %s \\begin{document} %s \\end{document}" + (or required-latex-imports "") + latex))) + (assert (s-contains-p (format "Output written on %s.pdf" (f-base file)) + (shell-command-to-string (format "pdflatex -shell-escape -halt-on-error %s; rm -f %s" file.tex file.tex))))) + ))))) + + + +(defun e2e--show-string-diff (string1 string2) + "Show the difference between STRING1 and STRING2 using `diff` in Emacs." + (interactive "sEnter first string: \nsEnter second string: ") + (let ((temp-buffer1 (get-buffer-create "*Diff String 1*")) + (temp-buffer2 (get-buffer-create "*Diff String 2*"))) + ;; Fill the first buffer with string1 + (with-current-buffer temp-buffer1 + (erase-buffer) + (insert string1)) + + ;; Fill the second buffer with string2 + (with-current-buffer temp-buffer2 + (erase-buffer) + (insert string2)) + + ;; Call the diff command on the two buffers + (diff temp-buffer1 temp-buffer2))) + + +(cl-defun e2e-run-tests (&optional (directory ".")) + (interactive) + "Run all e2e tests recursively in DIRECTORY." + (loop for file in (directory-files-recursively directory "\\.yaml\\'") + do (e2e--make-ert-test-for-yaml-file file)) + ;; Run all tests matching regex + (ert "e2e/.*")) + + + +(cl-defun e2e-update-tests () + (interactive) + ;; update all tests + (loop for file in (directory-files-recursively "." "\\.yaml\\'") + do (e2e-update-test file))) + +(cl-defun e2e-update-this-test () + (interactive) + (e2e-update-test (buffer-file-name))) + +(cl-defun e2e-update-test (file) + (let* ((yaml (e2e--read-yaml-file file)) + (input (map-elt yaml 'input)) + (no-prettier (map-elt yaml 'no-prettier)) + ;; Creating an alist and not a hash-table so that ordering matters, for yaml encoding + (actual (-concat (list (cons 'input input)) + (map-remove (lambda (k v) (member k '(input expectations))) yaml) + (list (cons 'expectations (map-into (compute-values input no-prettier) 'alist)))))) + (with-temp-file file + ;; (insert (yaml-encode actual)) ;; Nope, it does not honour new lines + (insert (e2e--yaml-encode-alist actual)) + (-let [format-all-formatters '(("YAML" prettier))] + (yaml-mode) (format-all-buffer))))) + + + +(defun e2e--yaml-encode-alist (alist &optional indent-level) + "Encode an ALIST as a YAML-like string with multiline support. +Newlines within values are formatted using `|-' style. +Ensures a blank line before the `expectations' key if present. +INDENT-LEVEL specifies the current indentation level, defaulting to 0." + (let ((indent-level (or indent-level 0)) + (first-key t)) ;; Track if it’s the first key + (mapconcat + (lambda (pair) + (let* ((key (car pair)) + (value (cdr pair)) + (indent (make-string (* 2 indent-level) ? )) + (sub-indent (make-string (* 2 (1+ indent-level)) ? )) + ;; Insert a blank line before `expectations` + (separator (if (and (eq key 'expectations) (not first-key)) + "\n\n" "\n"))) + (setq first-key nil) ;; No longer the first key after first iteration + (concat separator + indent (symbol-name key) ": " + (cond + ;; Multiline strings: use `|-` style with additional indentation + ((and (stringp value) (string-match-p "\n" value)) + (concat "|-\n" + sub-indent + (replace-regexp-in-string "\n" (concat "\n" sub-indent) value))) + ;; Nested alists: recursively call e2e--yaml-encode-alist + ((and (listp value) (listp (car value))) + (concat "\n" (e2e--yaml-encode-alist value (1+ indent-level)))) + ;; Other values: simply output the value + (t + (prin1-to-string value)))))) + alist + ""))) + + + +(defun compute-values (input no-prettier) + "Computes new output given INPUT; `no-prettier' means no prettier/auto-format of result is done." + (defun hs-hide-all () t) ;; HACK. PROBLEM: “hiding all blocks”, is this hide-show-mode? + (map-into + ;; Temporarily redefine gensym, so tests are deterministic +(let ((seed 0)) +(cl-letf (((symbol-function 'gensym) + (lambda () (format "g%s" (incf seed))))) + (list + (cons 'html + (with-temp-buffer + (org-special-block-extras-mode) + (insert (org-export-string-as (format "\n%s\n" input) 'html :body-only-please)) + (unless no-prettier + (-let [format-all-formatters '(("HTML" prettier))] + ;; TODO: When all my osbe e2e tests pass, then I should remove this ignore-errors. + ;; If something doesn't format, then that means it's likely invalid and should error. + ;; ignore-errors for unclosed

tags and other silly html errors + (html-mode) (ignore-errors (format-all-buffer)))) + (s-trim (buffer-string)))) + ;; Try to export, if it fails then just get the err msg. + (cons 'latex + (condition-case err + (with-temp-buffer + (org-special-block-extras-mode) + (insert (org-export-string-as (format "\n%s\n" input) 'latex :body-only-please)) + (unless no-prettier + (-let [format-all-formatters '(("LaTeX" latexindent))] ;; !! brew install latexindent + (latex-mode) (format-all-buffer))) + (s-trim (buffer-string))) + (error (format "🚫 The LaTex backend is intentionally unmaintained.\n🫠 Whoops, there seems to be an error: \n %S" err))))))) + 'hash-table)) + diff --git a/tests/fortune.yaml b/tests/fortune.yaml new file mode 100644 index 0000000..ed4d9de --- /dev/null +++ b/tests/fortune.yaml @@ -0,0 +1,13 @@ +fails: This link type works fine as a clickable element in Emacs, but fails on export. +priority: low +no-prettier: true +input: "[[fortune:cow][howdy buddo]]" +expectations: + html: |- +

+

 zsh:28: parse error near `|'
+    			cow 
+

+ latex: |- + input: |- + [[fortune:cow][howdy buddo]] diff --git a/tests/kbd.yaml b/tests/kbd.yaml new file mode 100644 index 0000000..710d157 --- /dev/null +++ b/tests/kbd.yaml @@ -0,0 +1,82 @@ +input: |- + + 1. It becomes tags, but final symbol non-ascii *may* be ignored + + kbd:C-u_80_-∀ + + 2. [ [ It ] ] becomes tags + + [[kbd:C-u_80_-]] + + 3. Using syntax becomes tags, and surrounding space is trimmed + + + + 4. It has a tooltip documenting the underlying Lisp function, when possible + + + +expectations: + html: |- +
    +
  1. +

    + It becomes <kbd> tags, but final symbol non-ascii may be + ignored +

    + +

    C-u 80_-∀

    +
  2. + +
  3. +

    [ [ It ] ] becomes <kbd> tags

    + +

    + C-u 80 - +

    +
  4. + +
  5. +

    + Using syntax <it> becomes <kbd> tags, and surrounding space is + trimmed +

    + +

    + C-u 80 - +

    +
  6. + +
  7. +

    + It has a tooltip documenting the underlying Lisp function, when possible +

    + +

    + M-s h . +

    +
  8. +
+ latex: |- + \begin{enumerate} + \item It becomes tags, but final symbol non-ascii \textbf{may} be ignored + + \texttt{C-u 80}\_-∀ + + \item {[} [ It ] ] becomes tags + + \texttt{C-u 80 -} + + \item Using syntax becomes tags, and surrounding space is trimmed + + \texttt{C-u 80 -} + + \item It has a tooltip documenting the underlying Lisp function, when possible + + \texttt{M-s h .} + \end{enumerate} diff --git a/tests/margin.yaml b/tests/margin.yaml new file mode 100644 index 0000000..d1cea2a --- /dev/null +++ b/tests/margin.yaml @@ -0,0 +1,73 @@ +input: |- + The ~doc~ link is for explanatory remarks, documentation, or personal glossary that is + intended to be used in multiple places. + + However, sometimes we want a “one-off” remark, containing, say, references or additional + explanation and we want it without the ceremony of first defining a glossary term + then invoking it. Enter the ~margin~ link. For example: + + + /Allah[[margin:][The God of Abraham; known as Elohim in the Bible]] does not + burden a soul beyond what it can bear./ + ---Quran 2:286 + + In HTML, such “marginal remarks” appear as tooltips; in LaTeX, they appear in the margin. + +expectations: + html: |- +

+ The doc link is for explanatory remarks, documentation, or + personal glossary that is intended to be used in multiple places. +

+ +

+ However, sometimes we want a “one-off” remark, containing, say, references or + additional explanation and we want it without the ceremony of first defining a + glossary term then invoking it. Enter the margin link. For + example: +

+ +

+ Allah°  does not burden a soul beyond what it can bear. + —Quran 2:286 +

+ +

+ In HTML, such “marginal remarks” appear as tooltips; in LaTeX, they appear in + the margin. +

+ latex: |- + The \texttt{doc} link is for explanatory remarks, documentation, or personal glossary that is + intended to be used in multiple places. + + However, sometimes we want a “one-off” remark, containing, say, references or additional + explanation and we want it without the ceremony of first defining a glossary term + then invoking it. Enter the \texttt{margin} link. For example: + + + \emph{Allah\!\!${}^{\textnormal{{\thefootnote}}}$ + \newsavebox{\OrgSpecialBlockExtrasMarginBox} + \begin{lrbox}{\OrgSpecialBlockExtrasMarginBox} + \begin{minipage}{\paperwidth - \textwidth - \oddsidemargin - 1in - 3ex} + \raggedright \iffalse Otherwise default alignment is fully justified. \fi + \footnotesize + \setminted{fontsize=\footnotesize, breaklines} \iffalse HACK! \fi + \color{gray!80} + {\color{black}${}^{\textnormal{{\thefootnote}}}$} + \normalfont + The God of Abraham; known as Elohim in the Bible + \end{minipage} + \end{lrbox} + \marginpar{\usebox{\OrgSpecialBlockExtrasMarginBox}\stepcounter{footnote}} + \hspace{-1.9ex} + \global\let\OrgSpecialBlockExtrasMarginBox\relax does not + burden a soul beyond what it can bear.} + ---Quran 2:286 + + In HTML, such “marginal remarks” appear as tooltips; in \LaTeX{}, they appear in the margin. diff --git a/tests/org-demo.yaml b/tests/org-demo.yaml new file mode 100644 index 0000000..2ea8019 --- /dev/null +++ b/tests/org-demo.yaml @@ -0,0 +1,87 @@ +input: |- + Sometimes, we want to show verbatim source and its resulting rendition ---which + is a major part of this article! So, let's make a block to mitigate such an + error-prone tedium. + + #+begin_org-demo + /italics/ and _underline_ + + $e^{i \times \pi} + 1 = 0$ + #+end_org-demo + + Output the CONTENTS of the block as both parsed Org and unparsed. + + Label the source text by SOURCE and the result text by RESULT + +expectations: + html: |- +

+ Sometimes, we want to show verbatim source and its resulting rendition + —which is a major part of this article! So, let's make a block to + mitigate such an error-prone tedium. +

+ +
+
+

Source

+
+
+    /italics/ and _underline_
+
+    $e^{i \times \pi} + 1 = 0$
+    
+
+
+
+

Result

+

italics and underline

+ +

\(e^{i \times \pi} + 1 = 0\)

+
+
+

Output the CONTENTS of the block as both parsed Org and unparsed.

+ +

Label the source text by SOURCE and the result text by RESULT

+ latex: |- + Sometimes, we want to show verbatim source and its resulting rendition ---which + is a major part of this article! So, let's make a block to mitigate such an + error-prone tedium. + + \par \setlength{\columnseprule}{0 pt} + \begin{minipage}[t]{\linewidth} + \begin{multicols}{2} + \begin{tcolorbox}[title={Source},colback="cyan",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}] + + \begin{verbatim} + /italics/ and _underline_ + + $e^{i \times \pi} + 1 = 0$ + + \end{verbatim} + \end{tcolorbox} + \begin{tcolorbox}[title={Result},colback="cyan",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}] + \emph{italics} and \uline{underline} + + \(e^{i \times \pi} + 1 = 0\) + + \end{tcolorbox} + + \end{multicols}\end{minipage} + Output the CONTENTS of the block as both parsed Org and unparsed. + + Label the source text by SOURCE and the result text by RESULT diff --git a/tests/parallel.yaml b/tests/parallel.yaml new file mode 100644 index 0000000..bbf4cd6 --- /dev/null +++ b/tests/parallel.yaml @@ -0,0 +1,185 @@ +input: |- + + In the HTML Export, The result is 2 columns with no rule between + them and it contains the user's text, but ignores the + “#+columnbreak”. + + #+begin_parallel + Hello, to the left! + + #+columnbreak: + A super duper wide middle margin! + + + #+columnbreak: + Goodbye (“God-be-with-ye”) to the right! + + #+columnbreak: + woah + #+end_parallel + + + [Soft Columns] Below, the result is 2 columns with a solid BLACK + rule between them and it contains the user's text, but ignores the + “#+columnbreak”. + + #+begin_parallel 2 :bar t + X + + #+columnbreak: + + Y + + Z + #+end_parallel + + + [Hard Columns] The result is 3 columns with a solid GREEN rule + between them and it contains the user's text along with the + “#+columnbreak” evaluated at the expected positions. + #+begin_parallel 20% 60% 20% :bar green X + + #+columnbreak: + + Y + + #+columnbreak: + + Z + #+end_parallel +no-prettier: t +expectations: + html: |- +

+ In the HTML Export, The result is 2 columns with no rule between + them and it contains the user's text, but ignores the + “#+columnbreak”. +

+ +
+

+ Hello, to the left! +

+ +

+ A super duper wide middle margin! +

+ + +

+ Goodbye (“God-be-with-ye”) to the right! +

+ +

+ woah +

+ +
+ + +

+ [Soft Columns] Below, the result is 2 columns with a solid BLACK + rule between them and it contains the user's text, but ignores the + “#+columnbreak”. +

+ +
+

+ X +

+ +

+ Y +

+ +

+ Z +

+ +
+ + +

+ [Hard Columns] The result is 3 columns with a solid GREEN rule + between them and it contains the user's text along with the + “#+columnbreak” evaluated at the expected positions. +

+
+ +

+

+

+ +

+ Y +

+ +

+

+

+ +

+ Z +

+ +
+ latex: |- + In the HTML Export, The result is 2 columns with no rule between + them and it contains the user's text, but ignores the + “\#+columnbreak”. + + \par \setlength{\columnseprule}{0 pt} + \begin{minipage}[t]{\linewidth} + \begin{multicols}{2} + Hello, to the left! + + \columnbreak + A super duper wide middle margin! + + + \columnbreak + Goodbye (“God-be-with-ye”) to the right! + + \columnbreak + woah + + + \end{multicols}\end{minipage} + + + {[}Soft Columns] Below, the result is 2 columns with a solid BLACK + rule between them and it contains the user's text, but ignores the + “\#+columnbreak”. + + \par \setlength{\columnseprule}{2 pt} + \begin{minipage}[t]{\linewidth} + \begin{multicols}{2} + X + + \columnbreak + + Y + + Z + + + \end{multicols}\end{minipage} + + + {[}Hard Columns] The result is 3 columns with a solid GREEN rule + between them and it contains the user's text along with the + “\#+columnbreak” evaluated at the expected positions. + \par \setlength{\columnseprule}{2 pt} + \begin{minipage}[t]{\linewidth} + \begin{multicols}{20% 60% 20%} + + \columnbreak + + Y + + \columnbreak + + Z + + + \end{multicols}\end{minipage} diff --git a/tests/rename.yaml b/tests/rename.yaml new file mode 100644 index 0000000..110453f --- /dev/null +++ b/tests/rename.yaml @@ -0,0 +1,42 @@ +input: |- + The ~rename~ block is for Textual Substitution. For example, as a translation tool: + + #+begin_rename "Allah to God, Yacoub to Jacob, Yusuf to Joseph" + Quran 12-4: *_Yusuf_* said to his father ( _*Yacoub*_ ), /“O my father, + indeed I have seen (in a dream) eleven stars and the sun and the + moon; I saw them prostrating to me.”/ + #+end_rename + + In the rendered result, the Arabic names are replaed with their English counterparts. + +expectations: + html: |- +

+ The rename block is for Textual Substitution. For example, as a + translation tool: +

+ +

+ Quran 12-4: Joseph said to his father ( + Jacob ), + “O my father, indeed I have seen (in a dream) eleven stars and the sun and + the moon; I saw them prostrating to me.” +

+ +

+ In the rendered result, the Arabic names are replaed with their English + counterparts. +

+ latex: |- + The \texttt{rename} block is for Textual Substitution. For example, as a translation tool: + + + Quran 12-4: \textbf{\uline{Joseph}} said to his father ( \uline{\textbf{Jacob}} ), \emph{“O my father, + indeed I have seen (in a dream) eleven stars and the sun and the + moon; I saw them prostrating to me.”} + + + + In the rendered result, the Arabic names are replaed with their English counterparts. diff --git a/tests/show.yaml b/tests/show.yaml new file mode 100644 index 0000000..987d214 --- /dev/null +++ b/tests/show.yaml @@ -0,0 +1,46 @@ +input: |- + + The ~doc~ link shows documentation, but to see the value we can use ~show~ as in: + + doc:user-full-name + show:user-full-name + + This allows us to have “generic” sentences: + + My name is show:user-full-name, + and I am using Emacs show:emacs-version ^_^ + My favourite number is + + To see values of expressions in other programming languages, use Org Babel inline source expressions. + +expectations: + html: |- +

+ The doc link shows documentation, but to see the value we can use + show as in: +

+ +

+ user-full-name + "Musa Al-hassy" +

+ +

This allows us to have “generic” sentences:

+ +

+ My name is "Musa Al-hassy", and I am using Emacs "29.4" ^_^ My favourite + number is 7.716814692820414 +

+ +

+ To see values of expressions in other programming languages, use Org Babel + inline source expressions. +

+ latex: |- + 🚫 The LaTex backend is intentionally unmaintained. + 🫠 Whoops, there seems to be an error: + (void-variable label) diff --git a/tests/solution.pdf b/tests/solution.pdf new file mode 100644 index 0000000..65a6de9 Binary files /dev/null and b/tests/solution.pdf differ diff --git a/tests/solution.yaml b/tests/solution.yaml new file mode 100644 index 0000000..017e0d7 --- /dev/null +++ b/tests/solution.yaml @@ -0,0 +1,117 @@ +input: |- + What is the name of Prophet Yusuf's father? + + Hint: The father's name also starts with `y`. + + #+begin_solution + Yacoub + #+end_solution +working-latex: |- + What is the name of Prophet Yusuf's father? + + Hint: The father's name also starts with `y`. + + \definecolor{osbe-bg}{HTML}{e5f5e5} + \colorlet{osbe-fg}{green} + + \begin{tcolorbox}[colback=osbe-bg, colframe=osbe-fg, title={Solution}, sharp corners, boxrule=0.4pt] + \begin{tcolorbox}[title={Did you actually try? Maybe see the ‘hints’ above!}, colback=blue!10!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}] + \begin{tcolorbox}[colback=osbe-bg, colframe=red, title={Solution, for real}, sharp corners, boxrule=0.4pt] + Yacoub + \end{tcolorbox} + \end{tcolorbox} + \end{tcolorbox} +explanation-of-changes-for-latex: |- + + The quote environment isn't designed to handle arbitrary content like tcolorbox. + Instead, I directly placed the tcolorbox environments without wrapping them in quote. + + Removed all colors specified in quotes (e.g., "blue"), as xcolor does not support them. + + 🚫 The LaTex backend is intentionally unmaintained +required-latex-imports: |- + \usepackage{newunicodechar} + \usepackage[hmargin=15mm,top=15mm,bottom=15mm]{geometry} + \usepackage[dvipsnames]{xcolor} + \usepackage{tcolorbox} + \usepackage{minted} + \usepackage{etoolbox} + \usepackage{multicol} + \usepackage{adjustbox} + + % TODO: Ideally the defblock itself would have these imports, + % For example, attached to the symbol's plist. + % Then use of the block would add the imports automatically. + % Moreover, we would not need this `required-latex-imports` key; + % instead it would be part of the `expectations` map, right before `latex`. + +expectations: + html: |- +

What is the name of Prophet Yusuf's father?

+ +

Hint: The father's name also starts with `y`.

+ +
+ + + Solution + + +
+

Did you actually try? Maybe see the ‘hints’ above!

+
+ + + Solution, for real + + +

Yacoub

+
+
+
+ latex: |- + What is the name of Prophet Yusuf's father? + + Hint: The father's name also starts with `y`. + + \definecolor{osbe-bg}{HTML}{e5f5e5}\colorlet{osbe-fg}{green}\begin{quote} + \begin{tcolorbox}[colback=osbe-bg,colframe=osbe-fg,title={Solution},sharp corners,boxrule=0.4pt] + \begin{tcolorbox}[title={Did you actually try? Maybe see the ‘hints’ above!},colback="blue",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}] + \definecolor{osbe-bg}{HTML}{e5f5e5}\colorlet{osbe-fg}{red}\begin{quote} + \begin{tcolorbox}[colback=osbe-bg,colframe=osbe-fg,title={Solution, for real},sharp corners,boxrule=0.4pt] + Yacoub + + + \end{tcolorbox} + \end{quote} + \end{tcolorbox} + + \end{tcolorbox} + \end{quote} diff --git a/tests/spoiler.yaml b/tests/spoiler.yaml new file mode 100644 index 0000000..223763e --- /dev/null +++ b/tests/spoiler.yaml @@ -0,0 +1,83 @@ +input: |- + The ~spoiler~ block is for “fill in the blanks” or “guess, and hover + to see the results” style of exposition. + + #+begin_spoiler + ((Yusuf)) said to his father ((Yacoub)), /“O my father, indeed I have seen + ((eleven stars)) and ((the sun and the moon)); I saw them prostrating to me.”/ + #+end_spoiler + + There are also options for the left delimiter of a hidden item, the right delimiter, + and the color of the hidden item. These default to ~grey, ((, ))~ respectively. + + #+begin_spoiler orange :left "[" :right "]" + [Yusuf] said to his father [Yacoub], /“O my father, indeed I have seen + [eleven stars] and [the sun and the moon]; I saw them prostrating to me.”/ + #+end_spoiler + +expectations: + html: |- +

+ The spoiler block is for “fill in the blanks” or “guess, and + hover to see the results” style of exposition. +

+ + +

+ Yusuf said to his father Yacoub , + “O my father, indeed I have seen eleven stars and + the sun and the moon ; I saw them prostrating to + me.” +

+ +

+ There are also options for the left delimiter of a hidden item, the right + delimiter, and the color of the hidden item. These default to + grey, ((, )) respectively. +

+ + +

+ Yusuf said to his father Yacoub , + “O my father, indeed I have seen eleven stars and + the sun and the moon ; I saw them prostrating to + me.” +

+ latex: |- + The \texttt{spoiler} block is for “fill in the blanks” or “guess, and hover + to see the results” style of exposition. + + + \fbox{\phantom{Yusuf}}\footnote{Yusuf} said to his father \fbox{\phantom{Yacoub}}\footnote{Yacoub}, \emph{“O my father, indeed I have seen + \fbox{\phantom{eleven stars}}\footnote{eleven stars} and \fbox{\phantom{the sun and the moon}}\footnote{the sun and the moon}; I saw them prostrating to me.”} + + + + There are also options for the left delimiter of a hidden item, the right delimiter, + and the color of the hidden item. These default to \texttt{grey, ((, ))} respectively. + + + \fbox{\phantom{Yusuf}}\footnote{Yusuf} said to his father \fbox{\phantom{Yacoub}}\footnote{Yacoub}, \emph{“O my father, indeed I have seen + \fbox{\phantom{eleven stars}}\footnote{eleven stars} and \fbox{\phantom{the sun and the moon}}\footnote{the sun and the moon}; I saw them prostrating to me.”} diff --git a/tests/tree.yaml b/tests/tree.yaml new file mode 100644 index 0000000..78ff896 --- /dev/null +++ b/tests/tree.yaml @@ -0,0 +1,34 @@ +input: |- + #+begin_box Programming ≈ Proving + #+begin_tree + + Function Application :: f(a) : B + - a : A + - f : A → B + + + Modus Ponens :: q + - p + - p ⇒ q + #+end_tree + #+end_box + +expectations: + html: |- +
+

Programming ≈ Proving

+ \[\frac{\displaystyle \qquad a : A \qquad f : A → B }{f(a) : B}[\text{Function + Application}]\]\[\frac{\displaystyle \qquad p \qquad p ⇒ q}{q}[\text{Modus + Ponens}]\] +
+ latex: |- + \begin{tcolorbox}[title={Programming ≈ Proving},colback=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}] + \[\frac{\displaystyle \qquad a : A \qquad f : A → B + }{f(a) : B}[\text{Function Application}]\]\[\frac{\displaystyle \qquad p \qquad p ⇒ q}{q}[\text{Modus Ponens}]\] + + \end{tcolorbox}