# -*- eval: (my/execute-startup-blocks) -*- #+title: org-special-block-extras #+subtitle: A unified interface for special blocks and links: defblock #+author: Musa Al-hassy #+PROPERTY: header-args:emacs-lisp :tangle org-special-block-extras.el :exports code #+options: d:nil toc:nil #+EXPORT_FILE_NAME: index #+macro: blurb Thirty new custom block and 34 link types for Emacs' Org-mode ^_^ #+PROPERTY: header-args :eval never-export # emacs -batch -l ert -l tests.el -f ert-run-tests-batch-and-exit # [[https://emacs.stackexchange.com/questions/32727/copy-to-clipboard-without-overwriting-kill-ring][functions - copy to clipboard without overwriting kill ring - Emacs Stack Exchange]] :30_blocks: 21 ⇒ 19 colours and the ‘colors’ block; & latex-definitions 3 ⇒ parallel, box, detail block 1 ⇒ editorial comments 4 ⇒ Solution, org-demo, spoiler, rename (textual substitution) 1 ⇒ documentation :End: :34_links: 20 ⇒ 19 colours and the ‘colors’ type 1 ⇒ editorial comments 1 ⇒ link here 8 ⇒ badges and socials 2 ⇒ doc and show 1 ⇒ kbd 1 ⇒ octoicon :End: # TODO make this function take a varidaic number of arguments, instead of a # solid list. Also, when called interactively allow users to input the name of a file. # (o-docs-load-libraries '("~/org-special-block-extras/documentation.org")) # (progn (load-file "org-special-block-extras.el") (org-special-block-extras-mode) (setq o--docs-libraries '("~/org-special-block-extras/documentation.org")) (ignore-errors (o-docs-load-libraries))) # MA: It seems one of these tests is effectful? # Restart Emacs and run the tests: The first time there will be a failure, but # the second time all tests pass. # # Do all tests continue to pass? # (progn (load-file "tests.el") (ert t)) # # emacs -batch -l ert -l tests.el -f ert-run-tests-batch-and-exit # # # “What to test?” # At least the stuff that I actually demo; e.g., the minimum working examples (MWE). # We can capture the ‘essence’ of a textual transformation by using s-matches? # with rx; e.g., see the red/colours tests. # # “⟰” is read “export”; it transform INPUT according to BACKEND for Org preprocessing; # INPUT is a multi-line string; we enclose it in ‘indent’. # It captures the idiom “with-temp-buffer, insert, org-special-block-extras export, buffer-string”. :No_tests_yet: 1. the exercises after defblock 2. defblock and its helpers; e.g., using defblock causes certain Lisp functions to be defined. ---Test should first make unbound such functions, ignoring errors. + There may be some tests under the Issue♯8 heading below. :End: # +TOC: headlines 2 # MA: Final version should not have the following incantation active. #+OPTIONS: broken-links:auto # MA: NEW, if a block type need something to be parsed as org-markup, # it should be wrapped in (org-parse ...)! # ... # maybe rename org-parse to org-markup! # (E.g., math to mathjax: surround it in org-markup!) # TODO MA: colour picker! # (1) get that nice colour picker working # (2) use /that/ code, from (1), to get red:X blue:Y etc working. # (defun my/ensure-headline-ids (&rest _) ) # (advice-remove 'org-html-export-to-html 'my/ensure-headline-ids) # (advice-remove 'org-md-export-to-markdown 'my/ensure-headline-ids) * HTML & LaTeX Setup :ignore: :PROPERTIES: :CUSTOM_ID: HTML-LaTeX-Setup :END: #+macro: newline @@latex: \newline@@ # (add-to-list 'org-src-lang-modes '("org" . c)) #+BEGIN_export html #+END_export #+latex_header: \usepackage{newunicodechar} #+latex_header: \newunicodechar{𝒳}{\ensuremath{\mathcal{X}}} #+latex_header: \newunicodechar{ℒ}{\ensuremath{\mathcal{L}}} #+LATEX_HEADER: \usepackage[hmargin=15mm,top=15mm,bottom=15mm]{geometry} #+latex_header: \usepackage{tcolorbox} #+latex_header: \newunicodechar{τ}{\ensuremath{\tau}} #+latex_header: \newunicodechar{⟨}{\ensuremath{\langle}} #+latex_header: \newunicodechar{⟩}{\ensuremath{\rangle}} #+latex_header: \newunicodechar{→}{\ensuremath{\to}} #+latex_header: \newunicodechar{⊕}{\ensuremath{\oplus}} #+latex_header: \newunicodechar{₀}{\ensuremath{_0}} #+latex_header: \newunicodechar{₁}{\ensuremath{_1}} #+latex_header: \newunicodechar{₂}{\ensuremath{_2}} #+latex_header: \newunicodechar{ₙ}{\ensuremath{_n}} #+latex_header: \newunicodechar{ₖ}{\ensuremath{_k}} #+latex_header: \newunicodechar{ᵢ}{\ensuremath{_i}} #+latex_header: \newunicodechar{′}{'} #+latex_header: \newunicodechar{⇒}{\ensuremath{\Rightarrow}} #+latex_header: \newunicodechar{𝒞}{\ensuremath{\mathcal{C}}} #+latex_header: \newunicodechar{∈}{\ensuremath{\in}} # (•̀ᴗ•́)و #+latex_header: \newunicodechar{و}{\ensuremath{;}} #+latex_header: \newunicodechar{•}{\ensuremath{\bullet}} #+latex_header: \newunicodechar{ᴗ}{\ensuremath{\smile}} #+latex_header: \newunicodechar{́}{\ensuremath{}} #+latex_header: \newunicodechar{̀}{\ensuremath{}} #+latex_header: \newunicodechar{‿}{\ensuremath{\smile}} #+latex_header: \newunicodechar{⌣}{\ensuremath{\smile}} #+latex_header: \newunicodechar{̈}{\ensuremath{{}^{..}}} #+LATEX_HEADER: \usepackage{minted} # +LATEX_HEADER: \usepackage{tcolorbox} # +LATEX_HEADER: \usepackage{etoolbox} # +LATEX_HEADER: \def\mytitle{??? Program Code ???} # +LATEX_HEADER: \BeforeBeginEnvironment{minted}{\begin{tcolorbox}[title=\hfill \mytitle]}% # +LATEX_HEADER: \AfterEndEnvironment{minted}{\end{tcolorbox}}% # # # Before a code block, write {{{code(title-of-block)}}} # # # +MACRO: code @@latex:\def\mytitle{$1}@@ # # # let's always break newlines, with a ‘↪’ indicated new lines. # emacs-lisp is treated as common-lisp via minted # +LaTeX: \setminted[common-lisp]{fontsize=\footnotesize, breaklines} #+LaTeX: \setminted[common-lisp]{breaklines} # Removing the red box that appears in "minted" when using unicode. # Src: https://tex.stackexchange.com/questions/343494/minted-red-box-around-greek-characters # #+LATEX_HEADER: \makeatletter #+LATEX_HEADER: \AtBeginEnvironment{minted}{\dontdofcolorbox} #+LATEX_HEADER: \def\dontdofcolorbox{\renewcommand\fcolorbox[4][]{##4}} #+LATEX_HEADER: \makeatother #+latex_header: \newunicodechar{𝓃}{\ensuremath{n}} #+latex_header: \newunicodechar{⋯}{\ensuremath{\cdots}} # +LATEX_HEADER: \usepackage[dvipsnames]{xcolor} % named colours #+latex_header: \definecolor{darkblue}{rgb}{0.0, 0.0, 0.55} #+LATEX_HEADER: \hypersetup{colorlinks,linkcolor=darkblue,citecolor=darkblue,urlcolor=darkblue} # https://taopeng.me/org-notes-style/ # # #+HTML_HEAD: # +SETUPFILE: https://fniessen.github.io/org-html-themes/setup/theme-readtheorg.setup #+latex_header: \usepackage{multicol} * Lisp Package Preamble :noexport: :PROPERTIES: :CUSTOM_ID: Preamble :END: #+BEGIN_SRC emacs-lisp :noweb yes ;;; org-special-block-extras.el --- 30 new custom blocks & 34 link types for Org-mode -*- lexical-binding: t; -*- ;; Copyright (c) 2021 Musa Al-hassy ;; Author: Musa Al-hassy ;; Version: 3.0 ;; Package-Requires: ((s "1.12.0") (dash "2.18.1") (emacs "26.1") (org "9.1") (lf "1.0")) ;; Keywords: org, blocks, colors, convenience ;; URL: https://alhassy.github.io/org-special-block-extras ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; This library provides common desirable features using the Org interface for ;; blocks and links: ;; ;; 0. A unified interface, the ‘defblock’ macro, for making new block and link types. ;; ;; 1. Colours: Regions of text and inline text can be coloured using 19 colours; ;; easily extendable; below is an example. ;; ;; #+begin_red org ;; /This/ ;; *text* ;; _is_ ;; red! ;; #+end_red ;; ;; 2. Multiple columns: Regions of text are exported into multiple side-by-side ;; columns ;; ;; 3. Remarks: First-class visible editor comments ;; ;; 4. Details: Regions of text can be folded away in HTML ;; ;; 5. Badges: SVG badges have the pleasant syntax ;; badge:key|value|colour|url|logo; only the first two are necessary. ;; ;; 6. Tooltips: Full access to Lisp documentation as tooltips, or any other ;; documentation-backend, including user-defined entries; e.g., doc:thread-first ;; retrives the documentation for thread-first and attachs it as a tooltip to ;; the text in the HTML export and as a glossary entry in the LaTeX export ;; ;; 7. Various other blocks: Solution, org-demo, spoiler (“fill in the blanks”). ;; ;; This file has been tangled from a literate, org-mode, file; and so contains ;; further examples demonstrating the special blocks it introduces. ;; ;; Full documentation can be found at ;; https://alhassy.github.io/org-special-block-extras ;;; Code: ;; String and list manipulation libraries ;; https://github.com/magnars/dash.el ;; https://github.com/magnars/s.el (require 's) ;; “The long lost Emacs string manipulation library” (require 'dash) ;; “A modern list library for Emacs” (require 'subr-x) ;; Extra Lisp functions; e.g., ‘when-let’. (require 'cl-lib) ;; New Common Lisp library; ‘cl-???’ forms. (require 'cus-edit) ;; To get the custom-* faces (require 'org) (require 'ox-latex) (require 'ox-html) (require 'lf) <> #+END_SRC # ;; Finally, the system is extensible: Users just use defblock! # ;; O--TYPE for a new custom block TYPE, which is then # ;; invoked. The handler takes three arguments: - CONTENTS: The string contents # ;; delimited by the custom block. - BACKEND: The current exportation backend; # ;; e.g., 'html or 'latex. The handler must return a string. # #+BEGIN_SRC emacs-lisp :noweb yes ;;;###autoload (define-minor-mode org-special-block-extras-mode "Provide 30 new custom blocks & 34 link types for Org-mode. All relevant Lisp functions are prefixed ‘o-’; e.g., `o-docs-insert'." nil nil nil (if org-special-block-extras-mode (progn <> ) ;; Must be on a new line; I'm using noweb-refs <> )) ;; Must be on a new line; I'm using noweb-refs #+END_SRC #+RESULTS: # With noweb, we need those new lines; otherwise in “x <> z” results in every # line of <> being prefixed by x and postfixed by z. # # # See https://github.com/alhassy/emacs.d#what-does-literate-programming-look-like * Testing :noexport: :PROPERTIES: :CUSTOM_ID: Testing :END: #+begin_src emacs-lisp :tangle tests.el (setq needed-libraries '(s cl-lib dash org seq quelpa lf)) (require 'package) (push '("melpa" . "https://melpa.org/packages/") package-archives) (package-initialize) (unless package-archive-contents (package-refresh-contents)) (dolist (pkg needed-libraries) (unless (package-installed-p pkg) (package-install pkg))) (load-file "org-special-block-extras.el") ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Personal Testing Utils ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (require 'lf) ;; to make use of “lf-string” (require 'ert) ;; to make use of “should” (cl-defun ⟰ (input &optional (backend 'html)) "Export Org INPUT along BACKEND. In particular, both org special blocks & links are exported into BACKEND. Pictogram explanation: ⟰ is read ‘export’; it “exports upward to the moon whatever we have”." (org-special-block-extras-mode) ;; Ensure new blocks are registered (org-export-string-as input backend :body-only)) (defmacro deftest (desc tags &rest body) "Declare tests with meaningful string names, that reflect the test's main goal. DESC is a string, TAGS is a vector. The first tag should be the name of the main function being tested; this name is prepended to the name of underlying ert-deftest. This way, tests are grouped/namespaced when running ert from the command line. I use Org-blocks with ‘:comments link’, this then serves to delimit my tests into “suites”. Example ERT call: (ert '(tag my-cool-tag))" `(ert-deftest ,(intern (concat (format "%s::" (seq-elt tags 0)) (seq-map (lambda (c) (if (<= 65 c 122) c ?_)) desc))) () :tags (quote ,(seq--into-list tags)) ,@body)) ;; Convert all non-letters to ‘_’; A = 65, z = 122. ;; Without the replace, “M-x ert” crashes when it comes to selecting the test ;; to run. (defmacro ≋ (lhs rhs) "A shorthand for (should (equal LHS RHS))." `(should (equal ,lhs ,rhs))) (defmacro ↯ (form &optional message) "The given FORM should error (with MESSAGE, ignoring whitespace). Example: (↯ (error \"hola\") \"hola\") Pictogram explanation: You've made such a big mistake, that the heavens strike you down with a lightening bolt ↯↯" (if message `(should (equal (s-collapse-whitespace (cl-second (should-error ,form))) (s-collapse-whitespace ,message))) `(should-error ,form))) ;; I like the shapes: (× some-crashing-form) and (↯ crashes with-this-message) (defalias '✓ 'should) (defalias '× '↯) (defmacro ⇝ (expr &rest regexp) "The given EXPR should match the given REGEXP, which is wrapped by ‘rx’. REGEXP could also be a string, in which case we are doing string equality. Either way, whitespace is ignored in both arguments. The symbol “⇝” should be read “rewrites to” or “elaborates to”. I prefer this form since it has the main form we're asserting against-at the forefront and makes it clear we're matching against strings. For example, (should (s-matches? \"An English greeting.\" (documentation 'speak))) Becomes: (⇝ (documentation 'speak) \"An English greeting.\") Pictogram explanation: A given expression “rewrites, reduces,” to a given matching pattern. Such arrows are popular in Term Rewriting Systems." `(should (s-matches? (s-collapse-whitespace (rx ,(cons 'seq regexp))) (s-collapse-whitespace ,expr)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; #+end_src -------------------------------------------------------------------------------- The following creates the “Github Actions Workflow” file; this way, Github will run your tests every time you commit ^_^ #+begin_src shell :tangle .github/workflows/main.yml # This workflow will do a clean install of dependencies and run tests # For more information see: https://help.github.com/actions/language-and-framework-guides/ name: Tests # Controls when the action will run. on: # Triggers the workflow on push or pull request events but only for the main branch push: branches: [ master ] pull_request: branches: [ master ] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: # This workflow contains a single job called "build" build: # The type of runner that the job will run on runs-on: ubuntu-latest # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v2 - name: Set up Emacs uses: purcell/setup-emacs@v3.0 with: # The version of Emacs to install, e.g. "24.3", or "snapshot" for a recent development version. version: 27.1 # optional # Runs a single command using the runners shell # - name: Run a one-line script # run: echo Hello, world! # Runs a set of commands using the runners shell # - name: Run a multi-line script # run: | # echo Add other actions to build, # echo test, and deploy your project. - name: Run tests run: emacs -batch -l ert -l tests.el -f ert-run-tests-batch-and-exit #+end_src # Upload coverage - uses: codecov/codecov-action@v1 * Abstract :ignore: :PROPERTIES: :CUSTOM_ID: Abstract :END: #+begin_center badge:Emacs|27|green|https://www.gnu.org/software/emacs|gnu-emacs badge:Org|9.4|blue|https://orgmode.org|gnu #+html: badge:org-special-block-extras|2.4|informational|https://github.com/alhassy/org-special-block-extras|Gnu-Emacs #+html: MELPA #+html: [[badge:license|GNU_3|informational|https://www.gnu.org/licenses/gpl-3.0.en.html|read-the-docs][gnu 3 license badge]] [[badge:docs|literate|success|https://github.com/alhassy/emacs.d#what-does-literate-programming-look-like|read-the-docs][read-the-docs badge]] tweet:https://github.com/alhassy/org-special-block-extras badge:contributions|welcome|green|https://github.com/alhassy/org-special-block-extras/issues badge:author|musa_al-hassy|purple|https://alhassy.github.io/|nintendo-3ds badge:|buy_me_a coffee|gray|https://www.buymeacoffee.com/alhassy|buy-me-a-coffee # badge:Hire|me|success|https://alhassy.github.io/about badge:EmacsConf|2020|informational|https://youtu.be/BQdNhtJSbqk|youtube #+end_center :Outdated_pics: #+begin_quote The full article may be read as a [[https://alhassy.github.io/org-special-block-extras/index.pdf][PDF]] or as [[https://alhassy.github.io/org-special-block-extras][HTML]] ---or visit the [[https://github.com/alhassy/org-special-block-extras][repo]]. Installation instructions are [[#Summary][below]]. #+end_quote #+caption: Extensibility! /Plug and play support for new block types!/ [[file:images/foo_block.png]] #+latex: \newpage /First, a gallery of what's possible!/ | *Write Org-markup once, generate for many backends ^_^* | [[file:images/colours.jpg]] [[file:images/colour_links.png]] #+latex: \newpage | *Displaying thoughts side-by-side ^_^* | [[file:images/parallel.png]] # | ( I use prettify symbols mode ) | #+latex: \newpage | *“First-class editor comments” In order: Chrome, Emacs Web Wowser, Org source, PDF* | [[file:images/edcomm.png]] #+latex: \newpage | *Visually hiding, folding away, details* | [[file:images/details.png]] # Broke_Org_9_4: #+latex: \newpage | *An Emacs interface to https://shields.io/* | [[file:images/badges.png]] # :End: # +latex: \newpage | *Tooltips for documentation and glossary items --in the browser!* | [[file:images/tooltips_browser.png]] # +latex: \newpage | *Tooltips for documentation and glossary items --in Emacs!* | [[file:images/tooltips_emacs.png]] # +latex: \newpage | *Tooltips for documentation and glossary items --in the PDF!* | [[file:images/tooltips_pdf.png]] # +latex: \newpage | *Declaring documentation-glossary items* | [[file:images/tooltips_declaration.png]] :End: #+begin_center *Abstract* #+end_center #+begin_quote The aim is to write something once using Org-mode markup then generate the markup for multiple backends. That is, /*write once, generate many!*/ In particular, we are concerned with /‘custom’, or ‘special’, blocks/ which delimit how a particular region of text is supposed to be formatted according to the possible export backends. In some sense, special blocks are meta-blocks. Rather than writing text in, say, LaTeX environments using LaTeX commands or in HTML =div='s using HTML tags, we promote using Org-mode markup in special blocks ---Org markup cannot be used explicitly within HTML or LaTeX environments. /Special blocks/, like ~centre~ and ~quote~, allow us to use Org-mode as the primary interface regardless of whether the final result is an HTML or PDF article; sometime we need to make our own special blocks to avoid a duplication of effort. However, this can be difficult and may require familiarity with relatively advanced ELisp concepts, such as macros and hooks; as such, users may not be willing to put in the time and instead use ad-hoc solutions. #+latex: \vspace{1em} We present a new macro, [[doc:o-defblock][defblock]], which is similar in-spirit to Lisp's standard doc:defun except that where the latter defines functions, ours defines new special blocks for Emacs' Org-mode ---as well as, simultaneously, defining new Org link types. Besides the macro, the primary contribution of this effort is an interface for special blocks that /admits/ arguments and is familar to Org users ---namely, we ‘try to reuse’ the familiar ~src~-block interface, including header-args, but for special blocks. #+latex: \vspace{1em} It is hoped that the ease of creating custom special blocks will be a gateway for many Emacs users to start using Lisp. #+latex: \iffalse *[[green:][A 5-page PDF covering ELisp fundamentals]]* can be found *[[https://alhassy.github.io/ElispCheatSheet/CheatSheet.pdf][here]]*. # a video lecture for this article can be found at # badge:EmacsConf|2020|informational|https://youtu.be/BQdNhtJSbqk|youtube. This article is featured in EmacsConf2020, with slides [[https://alhassy.github.io/org-special-block-extras/emacs-conf-2020][here]]: No pictures, instead we use this system to make the slides have a variety of styling information; i.e., we write Org and the result looks nice. “Look ma, no HTML required!” #+latex: \fi #+latex: \vspace{1em} # A tutorial on special blocks, link types, and this macro is found in the [[#A-unified-interface-for-special-blocks-and-links-defblock][first # section]] below. The remaining sections are examples of ~defblock~; namely, the # construction of the special blocks used in the tutorial ;-) # #+latex: \vspace{1em} #+latex: \textbf{Almost little to no attention has been afforded to making the PDF “look nice”; consider reading the HTML.} # Consequently, we extend the number of block types available to the Emacs # Org-mode user *without forcing the user* to learn HTML or LaTeX. # Indeed, I am not a web developer and had to learn a number of HTML concepts # in the process ---the average Org user should not have to do so. # # Similarly, we provide a number of ‘link types’ ~[[linktype:label][description]]~ # for producing in-line coloured text and SVG “badges”. # # We begin with the first two sections serving as mini-tutorials on special blocks # and on link types. The special block setup we use is /extensible/ in that a new # block named ~𝒞~ will automatically be supported if the user defines a function # ~o--𝒞~ that formats the text of a block. *The remaining # sections are literate implementation matter, along with examples and # screenshots.* # # In summary, we provide 20 colour block types and 20 colour link types, # an ‘editor comment’ block type as well as a link type, # a ‘details’ block type, a ‘parallel’ multiple columns view block type, # a ‘link here’ link type, 8 badge link types, # and block and link types for making documentation-glossary entries. # That is, *we provide 29 block types and 34 link types*. #+end_quote #+attr_html: :width 1000px #+attr_latex: :width 100px #+caption: Write in Emacs using Org-mode, export beautifully to HTML or LaTeX [[file:images/minimal-working-example-multiforms.png]] * org-tutorial :ignore: :PROPERTIES: :CUSTOM_ID: org-tutorial :END: #+include: ~/.emacs.d/init.org::#Mini-tutorial-on-Org-mode #+html: #+latex: \newpage #+TOC: headlines 2 #+begin_quote The full article may be read as a [[https://alhassy.github.io/org-special-block-extras/index.pdf][PDF]] or as [[https://alhassy.github.io/org-special-block-extras][HTML]] ---or visit the [[https://github.com/alhassy/org-special-block-extras][repo]]. Installation instructions are [[#Summary][below]]. #+end_quote #+latex: \newpage * A unified interface for special blocks and links: ~defblock~ :PROPERTIES: :CUSTOM_ID: A-unified-interface-for-special-blocks-and-links-defblock :END: **   /How do I make a new special block?/ ---Core Utility :ignore:introduction: :PROPERTIES: :CUSTOM_ID: How-do-I-make-a-new-special-block-Core-Utility :END: An Org-mode block of type 𝒳 is a bunch of text enclosed in ~#+begin_𝒳~ and ~#+end_𝒳~, upon export it modifies the text ---e.g., to center it, or to display it as code. We show how to make /new/ blocks using a simple interface ---[[doc:o-defblock][defblock]]--- that lets users treat 𝒳 as a string-valued function in the style of doc:defun. The notable features of the system are as follows. - Familiar ~defun~ syntax for making block ---~defblock~ - Familiar ~src~ syntax for passing arguments ---e.g., ~:key value~ # - Fine-grained control over export translation phases —c.f., ~org-parse~ above - Modular: New blocks can be made out of existing blocks really quickly using ~blockcall~ ---similar to Lisp's ~funcall~. #+begin_details EmacsConf 2020 Abstract # From https://emacsconf.org/2020/schedule/22/: Users will generally only make use of a few predefined /special blocks/, such as ~example, centre, quote~, and will not bother with the effort required to make new ones. When new encapsulating notions are required, users will either fallback on HTML or LaTeX specific solutions, usually littered with ~#+ATTR~ clauses to pass around configurations or parameters. Efforts have been exerted to mitigate the trouble of producing new special blocks. However, the issue of passing parameters is still handled in a clumsy fashion; e.g., by having parameters be expressed in a special block's content using specific keywords. We present a novel approach to making special blocks in a familiar fashion and their use also in a familiar fashion. We achieve the former by presenting ~defblock~, an anaphoric macro exceedingly similar to ~defun~, and for the latter we mimic the usual ~src~-block syntax for argument passing to support special blocks. For instance, here is a sample declaration. #+begin_src emacs-lisp :tangle no (defblock stutter () (reps 2) "Output the CONTENTS of the block REPS many times" (org-parse (s-repeat reps contents))) #+end_src Here is an invocation that passes an optional argument; which defaults to 2 when not given. #+begin_example org ,#+begin_stutter 5 Emacs for the win :-) ,#+end_stutter 5 #+end_example Upon export, to HTML or LaTeX for instance, the contents of this block are repeated (/stuttered/) 5 times. The use of ~src~-like invocation may lead to a decrease in ~#+ATTR~ clauses. In the presentation, we aim to show a few *practical* special blocks that users may want: A block that … - translates /some selected/ text ---useful for multilingual blogs - hides /some selected/ text ---useful for learning, quizzes - folds/boxes text ---useful in blogs for folding away details In particular, all of these examples will be around ~5 lines long! We also have a larger collection of more useful block types, already implemented. The notable features of the system are as follows. - Familiar ~defun~ syntax for making block ---~defblock~ - Familiar ~src~ syntax for passing arguments —e.g., ~:key value~ - Fine-grained control over export translation phases ---c.f., ~org-parse~ above - Modular: New blocks can be made out of existing blocks really quickly using ~blockcall~ ---similar to Lisp's ~funcall~. We will show how to fuse two blocks to make a new one, also within ~5 lines. #+latex: \iffalse *[[color:blue][It is hoped that the ease of creating custom special blocks will be a gateway for many Emacs users to start using Lisp.]]* #+latex: \fi #+end_details **   /What is a special block?/ :PROPERTIES: :CUSTOM_ID: What-is-a-special-block :END: An Org mode block is a region of text surrounded by =#+BEGIN_𝒳 … #+END_𝒳=; they serve various purposes as summarised in the table below. However, we shall *use such blocks to execute arbitrary code on their contents*. | 𝒳 | Description | |---------+----------------------------------------------------| | =example= | Format text verbatim, leaving markup as is | | =src= | Format source code | | =center= | Centre text | | =quote= | Format text as a quotation ---ignore line breaks | | =verse= | Every line is appended with a line break | | =tiny= | Render text in a small font; likewise =footnotesize= | | =comment= | Completely omit the text from export | - They can be folded and unfolded in Emacs by pressing TAB in the =#+BEGIN= line. - The contents of blocks can be highlighted as if they were of language ℒ such as =org, html, latex, haskell, lisp, python, …= by writing =#+BEGIN_𝒳 ℒ= on the starting line, where ~𝒳~ is the name of the block type. - Verbatim environments =src= and =example= may be followed by switch =-n= to display line numbers for their contents. I use [[https://github.com/alhassy/emacs.d#org-mode-templates-a-reason-i-generate-templates-][snippets in my init]] to quickly insert special blocks (•̀ᴗ•́)و #+begin_box You can ‘zoom in temporarily’, /narrowing/ your focus to only on a particular block, with doc:org-narrow-to-element, ~C-x n e~, to make your window only show the block. Then use ~C-x n w~ to /widen/ your vision of the buffer's contents. #+end_box #+begin_details Warning! Special blocks of the same kind do not nest! By their very nature, special blocks of /the same name/ cannot be nested ---e.g., try to put one ~quote~ block within another and see what (does not) happen. Moreover, special blocks cannot contain unicode in their names and no underscore, ‘_’, in their names; e.g., a special block named ~quote₀~ will actually refer to ~quote~. #+end_details Our goal is to turn Org blocks into LaTeX environments and HTML divs. Why not use LaTeX or HTML environments directly? - Can no longer use Org markup in such settings. - Committed to one specific export type. #+begin_remark Aside The export syntax =@@backend: 𝒳@@= inserts text 𝒳 literally as-is precisely when the current backend being exported to is =backend=. This is useful for inserting =html= snippets or =latex= commands. We can use =@@comment: 𝒳@@= to mimic inline comments ;-) ---Since there is [hopefully] no backend named =comment=. #+end_remark #+begin_box :background-color blue #+begin_parallel 3 In general, a “special block” such as #+begin_example org #+begin_𝒳 I /love/ Emacs! #+end_𝒳 #+end_example #+columnbreak: Exports to LaTeX as: #+begin_src latex :tangle no :exports code \begin{𝒳} I \emph{love} Emacs! \end{𝒳} #+end_src #+RESULTS: #+begin_export latex \begin{𝒳} I \emph{love} Emacs! \end{𝒳} #+end_export #+columnbreak: Exports to HTML as: #+begin_src html :tangle no
I love Emacs!
#+end_src #+end_parallel #+end_box #+begin_center /Notice that the standard org markup is also translated according to the export type./ #+end_center If the ~𝒳~ environment exists in a backend ---e.g., by some ~\usepackage{⋯}~ or manually with {{{newline}}} ~\newenvironment{𝒳}{⋯}{⋯}~ in LaTeX--- then the file will compile without error. Otherwise, you need to ensure it exists ---e.g., by defining the backend formatting manually yourself. #+latex: \vspace{1em} #+begin_remark Aside LaTeX packages that a user needs consistently are declared in the {{{newline}}} list ~org-latex-packages-alist~. See its documentation, with ~C-h o~, to learn more. To export to your own LaTeX classes, ~C-h o org-latex-classes~. #+end_remark #+latex: \vspace{1em} #+begin_box What is an HTML ‘div’? :background-color pink A ~div~ tag defines a division or a section in an HTML document that is styled in a particular fashion or has JavaScript code applied to it. For example ---placing the following in an ~#+begin_export html ⋯ #+end_export~--- results in a section of text that is editable by the user ---i.e., one can just alter text in-place--- and its foreground colour is red, while its background colour is light blue, and it has an uninformative tooltip. #+begin_parallel _Source_ #+begin_src html :tangle no
This is some editable text! Click me & type!
#+end_src #+html:
_Result_ #+html:

#+begin_export html
This is some editable text! Click me & type!
#+end_export #+end_parallel #+end_box #+begin_box What is a CSS ‘class’? To use a collection of style settings repeatedly, we may declare them in a =class= ---which is just an alias for the ;-separated list of =attribute:value= pairs. Then our ~div~'s refer to that particular ~class~ name. #+latex: \vspace{1em} #+begin_parallel :bar t For example, in an HTML export block, we may declare the following style class named ~red~. #+begin_example org #+begin_export html #+end_export #+end_example #+columnbreak: Now, the above syntax with ~𝒳~ replaced by ~red~ works as desired in HTML export: HTML now knows of a class named ~red~. #+latex: \vspace{1em} #+html:

For instance, now this #+begin_src C :tangle no ,#+begin_red I /love/ Emacs! ,#+end_red #+end_src Results in: #+begin_red I /love/ Emacs! #+end_red #+html:
#+latex: \vspace{1em} This approach, however, will not work if we want to produce LaTeX and so requires a duplication of efforts. We would need to declare such formatting once for each backend. #+end_parallel #+end_box **   /How do I make a new link type?/ :PROPERTIES: :CUSTOM_ID: Links :END: #+begin_center /Sometimes using a block is too verbose and it'd be better to ‘inline’ it; for this, we use Org's link mechanism./ #+end_center #+begin_box "What would I use links for?" :background-color custard + Buttons :: ~[[elisp:(find-file user-init-file)][Go to my init!]]~ is a nice clickable-/actionable/ button within Emacs. The =elisp= link is part of Emacs. #+html:
+ Textual expansions :: =show:user-full-name= expands, upon export, to /Musa Al-hassy/ on my machine. Or maybe we want a splash of colour in our lives: =**= becomes **. Similarly, ~link-here:usefulness-of-links~ becomes: link-here:usefulness-of-links. When you click on it, the URL of the webpage changes: It is a local anchor to a possibly interesting location in your article. Similarly, ~octoicon:home~ becomes octoicon:home; doc:o-link/octoicon. More aesthetically, == exports to . These link types are all defined below. #+html:
+ Definitions / Personal Glossary :: ~doc:thread-first, doc:family, doc:Hussain~ export as the link labels but with tooltips for the Lisp function doc:thread-first, for the English definition of doc:family, and my personal documentation for the phrase doc:Hussain. Similarly, ~[[kbd:M-s h .]]~ becomes [[kbd:M-s h .]], which has a tooltip for the Lisp function associated with the keybinding. # (o-docs-load-libraries (list "~/org-special-block-extras/documentation.org")) #+end_box #+begin_details Example Mini-Article Using The Above Link Types ( link-here:example-article-via-links ) kbd:C-x_ESC_ESC invokes doc:repeat-complex-command, you will see a Lisp form of the previous /interactive/ action you performed. You can press kbd:RET to repeat that command, or, more likely, to convert your actions to Lisp code for a personal function. This is a super neato way to get Lisp from your actions. Conversely, use to evaluate any Lisp you have in-hand 😉 badge:Emacs|is_so_cool|informational||gnu-emacs. #+end_details Just as there is doc:defun for “def”ining “fun”ctions and doc:defmacro for “def”ining “mac”ros, there is also doc:o-deflink for “def”ining “link”s for “O”rg, and it behaves similar to them. However, there are usually multiple ways to define things ---e.g., macros are code-valued functions defined with doc:cl-defun. Indeed, Org links are popularly defined using doc:org-link-set-parameters ---which, unlike doc:o-deflink, is not consistent with doc:defun--- and so let's take a look at it first. *** A comprehensive example of doc:org-link-set-parameters and doc:org-link-parameters :PROPERTIES: :CUSTOM_ID: A-comprehensive-example-of-doc-org-link-set-parameters-and-doc-org-link-parameters :END: Use src_emacs-lisp[:exports code]{(org-link-set-parameters params)} to add a new link type ---an older obsolete method is doc:org-add-link-type. The list of all supported link types is doc:org-link-parameters; its documentation identifies the possibilities for =params=. Let's produce an example link type, then discuss its code. Intended usage: Raw use example:salam and descriptive, [[example:hola][using ‘example’ link type]] ^_^ [[file:images/example_link.png]] # The “(ref:𝓍𝓍𝓍)” declarations are for line number referencing and not # part of the Lisp code needed to produce the example link type. # Consult the HTML/PDF rendition of this file or tangle the block below. # # #+name: startup-code #+begin_src emacs-lisp -n -r :tangle no (org-link-set-parameters ;; The name of the new link type, usage: “example:label” "example" (ref:extype) ;; When you click on such links, “let me google that for you” happens :follow (lambda (label) (browse-url (concat "https://lmgtfy.com/?q=" label))) (ref:exfollow) ;; Upon export, make it a “let me google that for you” link :export (lambda (label description backend) (ref:exexport) (format (pcase backend ('html "%s") ('latex "\\href{%s}{%s}") (_ "I don’t know how to export that!")) (concat "https://lmgtfy.com/?q=" label) (or description label))) ;; These links should *never* be folded in descriptive display; ;; i.e., “[[example:lable][description]]” will always appear verbatim ;; and not hide the first pair […]. ;; :display 'full (ref:exdisplay) ;; The tooltip alongside a link :help-echo (lambda (window object position) (ref:exhelpecho) (save-excursion (goto-char position) (-let* (((&plist :path :format :raw-link :contents-begin :contents-end) (cadr (org-element-context))) ;; (org-element-property :path (org-element-context)) (description (when (equal format 'bracket) (copy-region-as-kill contents-begin contents-end) (substring-no-properties (car kill-ring))))) (format "“%s” :: Let me google “%s” for you -__- %s and %s" raw-link (or description raw-link))))) ;; How should these links be displayed :face '(:foreground "red" :weight bold (ref:exface) :underline "orange" :overline "orange") ;; Any special keybindings when cursour is on this link type? ;; On ‘example:’ links, C-n/p to go to the next/previous such links. :keymap (let ((map (copy-keymap org-mouse-map))) ;; Populate the keymap (define-key map (kbd "C-p") (lambda () (interactive) (re-search-backward "example:" nil t))) (define-key map (kbd "C-n") (lambda () (interactive) (re-search-forward "example:" nil t))) ;; Return the keymap map)) #+end_src #+RESULTS: startup-code : o-html-export-preserving-whitespace + Line [[(extype)]] ="example"= :: Add a new =example= link type. - If the type already exists, update it with the given arguments. The syntax for a raw link is =example:path= and for the bracketed descriptive form ~[[example:path][description]]~. - Some of my intended uses for links including colouring text and doing nothing else, as such the terminology ‘path’ is not sufficiently generic and so I use the designation ‘label’ instead. + Line [[(exfollow)]] =:follow= :: What should happen when a user clicks on such links? This is a function taking the link path as the single argument and does whatever is necessary to “follow the link”, for example find a file or display a message. In our case, we open the user's browser and go to a particular URL. + Line [[(exexport)]] =:export= :: How should this link type be exported to HTML, LaTeX, etc? This is a three-argument function that formats the link according to the given backend, the resulting string value os placed literally into the exported file. Its arguments are: 1. =label= ⇒ the path of the link, the text after the link type prefix 2. =description= ⇒ the description of the link, if any 3. =backend= ⇒ the export format, a symbol like =html= or =latex= or =ascii=. In our example above, we return different values depending on the =backend= value. - If =:export= is not provided, default Org-link exportation happens. + Line [[(exdisplay)]] =:display= :: Should links be prettily folded away when a description is provided? + Line [[(exhelpecho)]] =:help-echo= :: What should happen when the user's mouse is over the link? This is *either a string or a string-valued function* that takes the current window, the current buffer object, and its position in the current window. In our example link, we go to the position of the object, destructure the Org link's properties using ~-let~, find the description of the link, if any, then provide a string based on the link's path and description. #+begin_details The general textual property ‘help-echo’ We may use ~help-echo~ to attach tooltips to arbitrary text in a file, as follows. I have found this to be useful in [[https://alhassy.github.io/next-700-module-systems/prototype/package-former.html][*metaprogramming*]] to have elaborated, generated, code shown as a tooltip attached to its named specification. #+begin_src emacs-lisp :tangle no ;; Nearly instantaneous display of tooltips. (setq tooltip-delay 0) ;; Give user 30 seconds before tooltip automatically disappears. (setq tooltip-hide-delay 300) (defun tooltipify (phrase notification &optional underline) "Add a tooltip to every instance of PHRASE to show NOTIFICATION. We only add tooltips to PHRASE as a standalone word, not as a subword. If UNDERLINE is provided, we underline the given PHRASE so as to provide a visual clue that it has a tooltip attched to it. The PHRASE is taken literally; no regexp operators are recognised." (assert (stringp phrase)) (assert (stringp notification)) (save-excursion ;; Return cursour to current-point afterwards. (goto-char 1) ;; The \b are for empty-string at the start or end of a word. (while (search-forward-regexp (format "\\b%s\\b" (regexp-quote phrase)) (point-max) t) ;; (add-text-properties x y ps) ;; ⇒ Override properties ps for all text between x and y. (add-text-properties (match-beginning 0) (match-end 0) (list 'help-echo (s-trim notification))))) ;; Example use (tooltipify "Line" "A sequential formatation of entities or the trace of a particle in linear motion") #+end_src We will use the tooltip doc:documentation later on ^_^ Useful info on tooltips: + [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Changing-Properties.html][Changing text properties ---GNU]] + [[http://kitchingroup.cheme.cmu.edu/blog/2013/04/12/Tool-tips-on-text-in-Emacs/][Tooltips on text in Emacs ---Kitchin]] + [[http://kitchingroup.cheme.cmu.edu/blog/2016/03/16/Getting-graphical-feedback-as-tooltips-in-Emacs/][Getting graphical feedback as tooltips in Emacs ---Kitchin]] + [[https://stackoverflow.com/questions/293853/defining-new-tooltips-in-emacs][Defining new tooltips in Emacs ---Stackoverflow]] #+end_details + Line [[(exface)]] =:face= :: What textual properties do these links possess? This is *either a face or a face-valued function* that takes the current link's path label as the only argument. That is, we could change the face according to the link's label ---which is what we will do for the =color= link type as in =[[color:brown][hello]]= will be rendered in brown text. - If ~:face~ is not provided, the default underlined blue face for Org links is used. - [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Faces.html][Learn more about faces!]] + More :: See =org-link-parameters= for documentation on more parameters. *** Define links as you define functions: doc:o-deflink :PROPERTIES: :CUSTOM_ID: NEW-o-deflink :END: For export purposes, an Org link can be thought of as a string-valued function whose inputs are the link's label, description, and the export backend. Since the input arguments are fixed, the ~o-deflink~ interface is similar to ~cl-defun~ in that it takes a name, (no input arguments), an optional docstring, and a body which may make use of (the unchanging input) names ~o-label, o-description, o-backend~. Moreover, after the documentation string, it may take an optional vector of display settings: Org links can be declared to look aesthetically pleasing within Emacs and to behave (via clicks/hovers/key-bindings) in meaningful ways. #+attr_html: :width 100% :height 100% [[file:./images/o-deflink.png]] Here is a minimal working example. After evaluating this, in an Org buffer, phrases such as ~shout:hello~ will (1) be shown in bold red, (2) have a hover tooltip that shows the documentation of the link /and/ an HTML export preview, and (3) the HTML export of the Org buffer will render the label ~hello~ as capitalised red text. #+begin_src emacs-lisp :tangle no (o-deflink shout "Capitalise the link description, if any, otherwise capitalise the label. The link text appears as red bold in both Emacs and in HTML export." [:face '(:foreground "red" :weight bold)] (format " %s " (upcase (or o-description o-label)))) #+end_src :Example_uses: angle bracket [[shout:][hello world!]] plain shout:hola [[shout: hello wolrd]] :End: #+begin_details Implementation of o-deflink #+begin_src emacs-lisp (cl-defmacro o-deflink (name &optional docstring display &rest body) "Make a new Org-link NAME that exports using form BODY. Since Org links are essentially string-valued functions, a function ‘o-link/NAME’ is created. DOCSTRING is optional; it is visible with (documentation 'o-link/NAME) BODY is a string-valued expression, that may make use of the names o-label, o-description, o-backend. The final one refers to the export backend, such as 'html or 'latex. The first two are obtained from uses: [[name:o-label][o-description]] In particular, the use case “name:o-label” means that o-description is nil. Example use: ;; In a Lisp buffer, press “C-x C-e” to load this definition (o-deflink shout (upcase (or o-description o-label))) ;; In an Org-buffer, press “C-c C-e h o” to see how this exports ;; Or using the bracket format [[shout:][hello world!]] [[shout: hello world!]] ;; or using the plain format shout:hello_world Here is a more complex, involved, example that makes use of ‘:let’ for local declarations. For instance, “define:hello” renders as the word “hello” with a tooltip defining the word; the definition is obtained from the command line tool ‘wn’. (o-deflink define \"Define the given word using WordNet, along with synonyms and coordinate terms.\" [:let (definition (shell-command-to-string (format \"wn %s -over -synsn -coorn\" o-label))) :help-echo definition] (--> definition (s-replace-regexp \"\\\\\\\"\" \"''\" it) ;; The presence of ‘\\\"’ in tooltips breaks things, so omit them. (s-replace-regexp \"\\n\" \"
\" it) (format \"%s\" it o-label))) For HTML tooltips, see `o-html-export-preserving-whitespace'. More generally, org-special-block-extra's “doc” link type supports, in order of precedence: User definitions, Emacs Lisp documentation of functions & variables, and definitions of English words. For example, “doc:existential_angst” for an entry ‘existential_angst’ whose associated documentation-glossary is user-defined in a ‘#+documentation’ Org-block, or “doc:thread-first” for the Emacs Lisp documentation of the function `thread-first', or “doc:user-mail-address” for the Emacs Lisp documentation of the variable `user-mail-address', or “doc:hello” for the definition of the English word ‘hello’. DISPLAY is a vector consisting of key-value pairs that affects how the link is displayed in Emacs Org buffers. The keys are as follows. + :help-echo is a string-valued expression of the tooltip that should accompany the new link in Org buffers. It has access to o-format being one of ‘plain’, ‘angle’, ‘bracket’ which indicates the format of the link, as shown above. It also has access to o-label and o-description. By default, the tooltip is the link name followed by the documentation of the link, and, finally, the HTML export of the link. That way, upon hover, users can visually see the link contents, know what/how the link exports, and actually see the HTML export. That is to say, for the ‘shout’ example aboce, the default display is essentially: [:help-echo (o-link/shout o-label o-description 'html)] You may want to add the following to your Emacs init file: ;; Nearly instantaneous display of tooltips. (setq tooltip-delay 0) ;; Give user 30 seconds before tooltip automatically disappears. (setq tooltip-hide-delay 300) + :face specifies how should these links be displayed within Emacs. It is a list-valued expression. As usual, it may make use of O-LABEL (but O-DESCRIPTION has value nil). + [:display full] if you do not want bracket links to be folded away in Org buffers; i.e., “[[X][Y]]” does not render as just “Y”. + :follow is a form that is executed when you click on such links; e.g., to open another buffer, browser, or other action. It makes use of (an implicit argument) ‘o-label’. Be aware that ‘o-label’ is a string that may contain spaces; e.g., when the action is to open a URL in a browser. If you are in need of providing similar, related, actions on a single link then your :follow can condition on the current prefix argument via ‘o-prefix’ (which is essentially `current-prefix-arg'). For instance, a user presses “C-u RET” on your link to do one thing but “C-u 72 RET” to do another action. + :keymap is an alternating list of keys and actions to be performed when those keys are pressed while point is on the link. For example: [:keymap (C-h (message-box \"hola\"))] By default, C-n and C-p are for moving to next and previous occruances of the same link type. + :let is a list of alternating variable symbol name and value, which are then used to form a concrete `let*' clause. This is useful for introducing local variables for use in the DISPLAY as well as in the CONTENTS. Such local declarations may make use of O-LABEL and O-DESCRIPTION, as usual." (cl-destructuring-bind (docstring display body) (lf-extract-optionals-from-rest docstring #'stringp display #'vectorp body) (setq display (seq--into-list display)) (let ((o-link/NAME (intern (format "o-link/%s" name))) (navigation "Press “C-h” to see possible actions on this link type.") (lets (cl-loop for (variable value) on (cl-getf display :let) by #'cddr collect (list variable value)))) `(progn ;; Declare the underlying function and documentation (cl-defun ,o-link/NAME ;; function name (o-label o-description o-backend) ;; function args ;; new function documentation ,docstring ;; function body (let* ,lets ,@body)) ;; Construct the Org-link (org-link-set-parameters ,(format "%s" name) :export (quote ,o-link/NAME) ;; How should these links be displayed? ;; (We augment the namespace with the missing o-description that local variables may be using.) :face (lambda (o-label) (let (o-description) (let* ,lets ,(cl-getf display :face)))) ;; When you click on such links, what should happen? ;; (We augment the namespace with the missing o-description that local variables may be using.) :follow (lambda (o-label o-prefix) (let (o-description) (let* ,lets ,(cl-getf display :follow)))) ;; These links should *never* be folded in descriptive display; ;; i.e., “[[example:lable][description]]” will always appear verbatim ;; and not hide the first pair […]. :display (quote ,(cl-getf display :display)) ;; e.g.,: 'full ;; Any special keybindings when cursour is on this link type? ;; On ‘NAME:’ links, C-n/p to go to the next/previous such links. :keymap (let ((o-keymap (copy-keymap org-mouse-map)) (pattern (format "%s:" (quote ,name))) (msg (concat (documentation (quote ,o-link/NAME)) "\nKEY BINDINGS:\n" "\nUnless indicated below otherwise..." "\n\tC-h: Shows this helpful message buffer" "\n\tC-n/C-p on the link to jump to next/previous links of this type;" "\n\tC-c C-x C-n/p for moving between arbitrary link types.\n\n" (pp-to-string (quote ,(cl-getf display :keymap)))))) ;; Populate the keymap (cl-loop for (key action) on (-cons* 'C-p `(re-search-backward ,pattern nil t) 'C-n `(re-search-forward ,pattern nil t) 'C-h `(-let [max-mini-window-height 0] ;; i.e., insist on displaying in a dedicated buffer (display-message-or-buffer ,msg)) (quote ,(cl-getf display :keymap))) by #'cddr do (define-key o-keymap (kbd (format "%s" key)) `(lambda () (interactive) ,action))) ;; Return the keymap o-keymap) ;; The tooltip alongside a link :help-echo (lambda (window object position) (save-excursion (goto-char position) (-let* (((&plist :path :format :contents-begin :contents-end) (cadr (org-element-context))) (o-format format) (o-label path) (o-description (when (equal format 'bracket) (copy-region-as-kill contents-begin contents-end) (substring-no-properties (car kill-ring))))) (or (let* ,lets ,(cl-getf display :help-echo)) (format "%s:%s\n\n%s\nHTML Export:\n\n%s" (quote ,name) (or o-description o-label) ,(concat (or docstring "") "\n\n" navigation "\n") (,o-link/NAME o-label o-description 'html))))))) ;; Return value is the name of the underlying function. ;; We do this to be consistent with `defun'. (quote ,o-link/NAME))))) #+end_src # shout:hi Let's have some sanity tests... #+begin_src emacs-lisp :tangle tests.el :comments link (o-deflink shout "Capitalise the link description, if any, otherwise capitalise the label. The link text appears as red bold in both Emacs and in HTML export." [:face '(:foreground "red" :weight bold) ;; :help-echo (o-link/shout o-label o-description 'html) :display full :keymap (C-m (message-box "hola")) :follow (message-box "%s and %s" pre current-prefix-arg) ] (format " %s " (upcase (or o-description o-label)))) (deftest "o-deflink makes documented functions" [o-deflink] (⇝ (documentation #'o-link/shout) "Capitalise the link description, if any, otherwise capitalise the label. The link text appears as red bold in both Emacs and in HTML export.")) (deftest "o-deflink works as expected, plain links" [o-deflink] (should (not (null (symbol-function 'o-link/shout)))) (⇝ (⟰ "shout:hello") "

HELLO

")) (deftest "o-deflink works as expected, bracket links" [o-deflink] (⇝ (⟰ "[[shout:hello]]") "

HELLO

") (⇝ (⟰ "[[shout:hello][world!]]") "

WORLD!

")) (deftest "o-deflink works as expected, angle links" [o-deflink] (⇝ (⟰ "") "

HELLO WORLD!

")) #+end_src #+end_details **** COMMENT Example links... :TestingPurposes:Already_in_docs: :PROPERTIES: :CUSTOM_ID: COMMENT-Example-links :END: #+begin_src emacs-lisp (o-deflink define "Define the given word using WordNet, along with synonyms and coordinate terms." [:help-echo (shell-command-to-string (format "wn %s -over -synsn -coorn" o-label))] (--> o-label (shell-command-to-string (format "wn %s -over -coorn" it)) (s-replace-regexp "\\\"" "''" it) ;; The presence of ‘\"’ in tooltips breaks things, so omit them. (s-replace-regexp "\n" "
" it) (format "%s" it o-label))) #+end_src #+begin_src emacs-lisp (o-deflink shout "Capitalise the link description, if any, otherwise capitalise the label. The link text appears as red bold in both Emacs and in HTML export." [:face '(:foreground "red" :weight bold)] (format " %s " (upcase o-label))) #+end_src [[# orange:Going forward]] it would be nice to make use of doc:org-edit-special to # edit the various link types introduced in this article. ** The Core Utility: ~defblock~ and friends :PROPERTIES: :CUSTOM_ID: The-Core-Utility-defblock-and-friends :END: #+latex_header: \newunicodechar{≈}{\ensuremath{\approx}} #+latex_header: \newunicodechar{⟦}{\ensuremath{[\![}} #+latex_header: \newunicodechar{⟧}{\ensuremath{]\!]}} #+latex_header: \newunicodechar{★}{\ensuremath{\star}} #+latex_header: \newunicodechar{⇨}{\ensuremath{\Rightarrow}} #+latex_header: \newunicodechar{⇦}{\ensuremath{\Leftarrow}} #+latex_header: \newunicodechar{↦}{\ensuremath{\mapsto}} To have a unified, and pleasant, interface for declaring new blocks and links, we take the following approach: 0. [@0] ( /Fuse/ the process of link generation and special block support into one macro, [[doc:o-defblock][defblock]] which is like doc:defun. ) 1. The user writes as string-valued function named 𝒳, possibly with arguments, that has access to a ~contents~ and ~backend~ variables. :Posterity_OLD_org_export_parse: (defun org-export (x) "Wrap the given X in an export block for the current backend." (format "\n#+begin_export %s \n%s\n#+end_export\n" (if (equal o--current-backend 'reveal) 'html o--current-backend) x)) (defun o--org-parse (x) "This should ONLY be called within an ORG-EXPORT call." (format "\n#+end_export\n%s\n#+begin_export %s\n" x (if (equal 'reveal o--current-backend) 'html o--current-backend))) :End: #+begin_details ‘defblock’ Implementation #+begin_src emacs-lisp (defvar o--supported-blocks nil "Which special blocks, defined with DEFBLOCK, are supported.") (cl-defmacro o-defblock (name main-arg kwds &optional experimental docstring &rest body) "Declare a new special block, and link, in the style of DEFUN. A full featured example is at the end of this documentation string. This is an anaphoric macro that provides export support for special blocks *and* links named NAME. Just as an Org-mode src-block consumes as main argument the language for the src block, our special blocks too consume a MAIN-ARG; it may be a symbol or a cons-list consisting of a symbolic name (with which to refer to the main argument in the definition of the block) followed by a default value, then, optionally, any information for a one-time setup of the associated link type. The main arg may be a sequence of symbols separated by spaces, and a few punctuation with the exception of comma ‘,’ since it is a special Lisp operator. In doubt, enclose the main arg in quotes. Then, just as Org-mode src blocks consume key-value pairs, our special blocks consume a number of KWDS, which is a list of the form (key₀ value₀ … keyₙ valueₙ). After that is an optional DOCSTRING, a familar feature of DEFUN. The docstring is displayed as part of the tooltip for the produced link type. Finally, the BODY is a (sequence of) Lisp forms ---no progn needed--- that may refer to the names BACKEND and CONTENTS which refer to the current export backend and the contents of the special block ---or the description clause of a link. CONTENTS refers to an Org-mode parsed string; i.e., Org-markup is acknowledged. In, hopefully, rare circumstances, one may refer to RAW-CONTENTS to look at the fully unparsed contents. Finally, this macro exposes two functions: + ORG-EXPORT: Wrap the argument in an export block for the current backend. + ORG-PARSE: This should ONLY be called within an ORG-EXPORT call, to escape text to Org, and out of the export block. ---------------------------------------------------------------------- TLDR for EXPERIMENTAL and DOCSTRING and BODY, the first two parts are optional; they're a symbol, a string, then the main body. The symbol, O-RESPECT-NEWLINES?, when present enables a highly experimental [i.e., do *not* use it!] feature: No new lines for blocks in HTML export. Its need rose from developing the MARGIN block type. ---------------------------------------------------------------------- The relationship between links and special blocks: [ [type:label][description]] ≈ ,#+begin_type label description ,#+end_type ---------------------------------------------------------------------- Example declaration, with all possible features shown: ;; We can use variable values when defining new blocks (setq angry-red '(:foreground \"red\" :weight bold)) (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.\" (format (if (equal backend 'html) \"⟦%s: %s%s⟧\" \"{\\color{%s}\\bfseries %s: %s%s}\") color editor contents signoff)) ;; I don't want to change the definition, but I'd like to have ;; the following as personalised defaults for the “remark” block. ;; OR, I'd like to set this for links, which do not have argument options. (defblock-header-args remark :main-arg \"Jasim Jameson\" :signoff \"( Aim for success! )\") Three example uses: ;; ⟨0⟩ As a special blocks with arguments given. ,#+begin_remark Bobbert Barakallah :signoff \"Thank-you for pointing this out!\" :color green I was trying to explain that ${\large (n × (n + 1) \over 2}$ is always an integer. ,#+end_remark ;; ⟨1⟩ As a terse link, using default values for the args. ;; Notice that Org-mode formatting is recoqgnised even in links. [ [remark:Jasim Jameson][Why are you taking about “$\mathsf{even}$” here?]] ;; ⟨2⟩ So terse that no editor name is provided. [ [remark:][Please improve your transition sentences.]] ;; ⟨★⟩ Unlike 0, examples 1 and 2 will have the default SIGNOFF ;; catenated as well as the default red color." ;; ⇨ The special block support ;; (add-to-list 'o--supported-blocks name) ;; global var ;; Identify which of the optional features is present... (cl-destructuring-bind (o-respect-newlines? docstring body) (lf-extract-optionals-from-rest experimental #'keywordp docstring #'stringp body) `(progn ;; Produce an associated Lisp function ,(o-defblock---support-block-type name docstring (if (consp `,main-arg) (car main-arg) 'main-arg) ;; main argument's name (cadr main-arg) ;; main argument's value kwds body ;; MA: I'd like it to be always ‘true’, but it's experimental and breaks so much stuff. o-respect-newlines? ) ;; ⇨ The link type support ;; The ‘main-arg’ may contain a special key ‘:link-type’ whose contents ;; are dumped here verbatim. ;; ‘(main-arg-name main-arg-val :face … :follow …)’ (o-deflink ,name [:help-echo (format "%s:%s\n\n%s" (quote ,name) o-label ,docstring) ,@(cddr main-arg) ;; verbatim link extras ] ;; s-replace-all `(("#+end_export" . "") (,(format "#+begin_export %s" backend) . "")) (s-replace-all `(("@@" . "")) ;; (,(format "@@%s:" backend) . "") (,(intern (format "o--%s" name)) o-backend (or o-description o-label) o-label :o-link? t)))))) ;; WHERE ... (cl-defmethod o-defblock---support-block-type (name docstring main-arg-name main-arg-value kwds body o-respect-newlines?) "Helper method for o-defblock. This method creates an Org block type's associated Lisp function. NAME, string: The name of the block type. DOCSTRING, string: Documentation of block. MAIN-ARG-NAME: Essentially main-arg's name MAIN-ARG-VALUE: Essentially main-arg's value KWDS, plist: Keyword-value pairs BODY, list: Code to be executed" `(cl-defun ,(intern (format "o--%s" name)) (backend raw-contents &optional ;; ,(car main-arg) ,main-arg-name &rest _ &key (o-link? nil) ,@(-partition 2 kwds)) ,docstring ;; Use default for main argument (when (and ',main-arg-name (s-blank-p ,main-arg-name)) (--if-let (plist-get (cdr (assoc ',name o--header-args)) :main-arg) (setq ,main-arg-name it) (setq ,main-arg-name ,main-arg-value))) (cl-letf (((symbol-function 'org-export) (lambda (x) "Wrap the given X in an export block for the current backend. One can think of this function as replacing the #+begin_𝒳⋯#+end_𝒳 in-place in your Org document; but really that's done by the ⋯-support-blocks function. " (if o-link? x ;; o-respect-newlines? is super experimental: It's a bit ugly on the LaTeX side. (cond ((and ,o-respect-newlines? (member backend '(html reveal))) (format "@@%s:%s@@" backend (s-replace "\n" (format "@@\n@@%s:" backend) x) backend)) (:else (format "#+begin_export %s \n%s\n#+end_export" backend x)))))) ((symbol-function 'org-parse) (lambda (x) "This should ONLY be called within an ORG-EXPORT call." (if o-link? x (cond ((and ,o-respect-newlines? (member backend '(html reveal))) (format "@@%s@@%s:" x backend)) (:else (format "\n#+end_export\n%s\n#+begin_export %s\n" x backend))))))) ;; Use any headers for this block type, if no local value is passed ,@(cl-loop for k in (mapcar #'car (-partition 2 kwds)) collect `(--when-let (plist-get (cdr (assoc ',name o--header-args)) ,(intern (format ":%s" k))) (when (s-blank-p ,k) (setq ,k it)))) (org-export (let ((contents (org-parse raw-contents))) ,@body))))) #+end_src #+RESULTS: : o-defblock---support-link-type # (fmakunbound 'o--org-parse) # [[color:orange][Going forward,]] Going forward, it would be nice to have a set of switches that apply to all special blocks. For instance, ~:ignore~ to simply bypass the user-defined behaviour of a block type, and ~:noexport~ to zero-out a block upon export. These are super easy to do ---just need a few minutes to breath. It may also be desirable to provide support for [[https://github.com/alhassy/emacs.d#html-folded-drawers][drawers]] ---just as we did to ‘fuse’ the block-type and link-type approaches used here into one macro. #+end_details 2. [@2] We tell Org to please look at all special blocks #+begin_src org :tangle no ,#+begin_𝒳 main-arg :key₀ value₀ … :keyₙ valueₙ contents ,#+end_𝒳 #+end_src Then, before export happens, to replace all such blocks with the /result/ of calling the user's 𝒳 function; i.e., replace them by, essentially, #+begin_src emacs-lisp :tangle no (𝒳 main-arg :key₀ value₀ … :keyₙ valueₙ :o-contents contents) #+end_src #+begin_details Implementing the hooking mechanism The mechanism that rewrites your source... #+begin_src emacs-lisp (defun o--pp-list (xs) "Given XS as (x₁ x₂ … xₙ), yield the string “x₁ x₂ … xₙ”, no parens. When n = 0, yield the empty string “”." (s-chop-suffix ")" (s-chop-prefix "(" (format "%s" (or xs ""))))) (defvar o--current-backend nil "A message-passing channel updated by o--support-special-blocks-with-args and used by DEFBLOCK.") (defun o--support-special-blocks-with-args (backend) "Remove all headlines in the current buffer. BACKEND is the export back-end being used, as a symbol." (setq o--current-backend backend) (let (blk-start ;; The point at which the user's block begins. header-start ;; The point at which the user's block header & args begin. kwdargs ;; The actual key-value arguments for the header. main-arg ;; The first (non-keyed) value to the block. blk-column ;; The column at which the user's block begins. body-start ;; The starting line of the user's block. blk-contents ;; The actual body string. ;; ⟨blk-start/column⟩#+begin_⟨header-start⟩blk main-arg :key₀ val ₀ … :keyₙ valₙ ;; ⟵ ⟨kwdargs⟩ ;; ⟨body-start⟩ body ;; #+end_blk ) (cl-loop for blk in o--supported-blocks do (goto-char (point-min)) (while (ignore-errors (re-search-forward (format "^\s*\\#\\+begin_%s" blk))) ;; MA: HACK: Instead of a space, it should be any non-whitespace, optionally; ;; otherwise it may accidentlly rewrite blocks with one being a prefix of the other! (setq header-start (point)) ;; Save indentation (re-search-backward (format "\\#\\+begin_%s" blk)) (setq blk-start (point)) (setq blk-column (current-column)) ;; actually process body (goto-char header-start) (setq body-start (1+ (line-end-position))) (thread-last (buffer-substring-no-properties header-start (line-end-position)) (format "(%s)") read (--split-with (not (keywordp it))) (setq kwdargs)) (setq main-arg (o--pp-list (car kwdargs))) (setq kwdargs (cadr kwdargs)) (forward-line -1) (re-search-forward (format "^\s*\\#\\+end_%s" blk)) (setq blk-contents (buffer-substring-no-properties body-start (line-beginning-position))) (kill-region blk-start (point)) (insert (eval `(,(intern (format "o--%s" blk)) (quote ,backend) ,blk-contents ,main-arg ,@(--map (list 'quote it) kwdargs)))) ;; See: https://github.com/alhassy/org-special-block-extras/issues/8 ;; (indent-region blk-start (point) blk-column) ;; Actually, this may be needed... ;; (indent-line-to blk-column) ;; #+end... ;; (goto-char blk-start) (indent-line-to blk-column) ;; #+begin... ;; the --map is so that arguments may be passed ;; as "this" or just ‘this’ (raw symbols) )))) #+end_src Let's have some sanity tests... #+begin_src emacs-lisp :tangle old_tests.el (deftest "pp-list works as desired" (should (equal "1 2 3 4 5" (o--pp-list '(1 2 3 4 5)))) (should (equal "1" (o--pp-list '(1)))) (should (equal "" (o--pp-list nil)))) ;; Using propcheck, we run this test on /arbitrary/ buffer contents. (deftest "No supported blocks means buffer is unchanged" :tags '(core) (let* (o--supported-blocks (propcheck-seed (propcheck-seed)) (buf (propcheck-generate-string nil))) (should (equal buf (with-temp-buffer (insert buf) (o--support-special-blocks-with-args 'html) (buffer-string)))))) (deftest "Constant blocks preserve indentation/enumeration" :expected-result :failed :tags '(core) (defblock go nil nil "doc" "hello") ;; Constantly “hello” (should (equal " 1. item one 2. item two ,#+begin_export html hello ,#+end_export 3. item three" (with-temp-buffer (insert " 1. item one 2. item two ,#+begin_go world ,#+end_go 3. item three") (o--support-special-blocks-with-args 'html) (buffer-string))))) (deftest "Constant blocks export to LaTex preserves indentation/enumeration" (should (equal "\\begin{enumerate} \\item item one \\item item two hello \\item item three \\end{enumerate} " (org-export-string-as " 1. item one 2. item two ,#+begin_go world ,#+end_go 3. item three" 'latex :body-only-please)))) (deftest "Constant blocks export to HTML preserves indentation/enumeration" (should (equal "
  1. item one
  2. item two

    hello
  3. item three
" (org-export-string-as " 1. item one 2. item two ,#+begin_go world ,#+end_go 3. item three" 'html :body-only-please)))) (deftest "Identity blocks preserve indentation/enumeration" :expected-result :failed :tags '(core) (defblock id nil nil "doc" contents) (should (equal " 1. item one 2. item two ,#+begin_export html ,#+end_export world ,#+begin_export html ,#+end_export 3. item three" (with-temp-buffer (insert " 1. item one 2. item two ,#+begin_id world ,#+end_id 3. item three") (o--support-special-blocks-with-args 'html) (buffer-string))))) (deftest "Identity blocks export to LaTex preserves indentation/enumeration" :expected-result :failed (defblock id nil nil "doc" contents) (should (equal "\\begin{enumerate} \\item item one \\item item two world \\item item three \\end{enumerate} " (org-export-string-as " 1. item one 2. item two ,#+begin_id world ,#+end_id 3. item three" 'latex :body-only-please)))) (deftest "Identity blocks export to HTML preserves indentation/enumeration" :expected-result :failed (defblock id nil nil "doc" contents) (should (equal "
  1. item one
  2. item two

    world

  3. item three
" (org-export-string-as " 1. item one 2. item two ,#+begin_id world ,#+end_id 3. item three" 'html :body-only-please)))) #+end_src When you enable the ~org-special-block-extras~ mode, it is activated... #+begin_src emacs-lisp :noweb-ref enable-mode :tangle no ;; https://orgmode.org/manual/Advanced-Export-Configuration.html (add-hook 'org-export-before-parsing-hook 'o--support-special-blocks-with-args) #+end_src #+RESULTS: | o--support-special-blocks-with-args | org-ref-acronyms-before-parsing | org-ref-glossary-before-parsing | When you disable the ~org-special-block-extras~ mode, it is deactivated... #+BEGIN_SRC emacs-lisp :noweb-ref disable-mode :tangle no (remove-hook 'org-export-before-parsing-hook 'o--support-special-blocks-with-args) #+END_SRC #+RESULTS: | org-ref-acronyms-before-parsing | org-ref-glossary-before-parsing | #+end_details #+begin_details ‘header-args’ Implementation Then: #+begin_src emacs-lisp (defvar o--header-args nil "Alist (name plist) where “:main-arg” is a special plist key. It serves a similar role to that of Org's src ‘header-args’. See doc of SET-BLOCK-HEADER-ARGS for more information.") (defmacro o-set-block-header-args (blk &rest kvs) "Set default valuts for special block arguments. This is similar to, and inspired by, Org-src block header-args. Example src use: ,#+PROPERTY: header-args:Language :key value Example block use: (set-block-header-args Block :main-arg mainvalue :key value) A full, working, example can be seen by “C-h o RET defblock”. " `(add-to-list 'o--header-args (list (quote ,blk) ,@kvs))) #+end_src #+end_details This interface is essentially that of Org's ~src~ blocks, with the ~main-arg~ being the first argument to 𝒳 and the only argument not needing to be preceded by a key name ---it is done this way to remain somewhat consistent with the Org ~src~ interface. The user definition of 𝒳 decides on /how optional/ the arguments actually are. Perhaps an example will clarify things ... *** Example: Jasim providing in-place feedback to Bobbert :PROPERTIES: :CUSTOM_ID: Example-Jasim-providing-in-place-feedback-to-Bobbert :END: Suppose we want to devise a simple special block for editors to provide constructive feedback to authors so that the feedback appears as top-level elements of the resulting exported file ---instead of comments that may accidentally not be handled by the author. In order to showcase the multiple bells and whistles of the system, the snippet below is twice as long than it needs to be, but it is still reasonably small and accessible. # Not anymore: ( The documentation string to ~defblock~ is mandatory. ) #+begin_src emacs-lisp :tangle no ;; We can use variable values when defining new blocks (setq angry-red '(:foreground "red" :weight bold)) ;; This is our 𝒳, “remark”. ;; As a link, it should be shown angry-red; ;; it takes two arguments: “color” and “signoff” ;; with default values being "red" and "". (o-defblock rremark (editor "Editor Remark" :face angry-red) (color "red" signoff "") "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}") color editor contents signoff)) ;; I don't want to change the definition, but I'd like to have ;; the following as personalised defaults for the “remark” block. ;; OR, I'd like to set this for links, which do not have argument options. (o-set-block-header-args rremark :main-arg "Jasim Jameson" :signoff "( Aim for success! )") #+end_src #+RESULTS: | rremark | :main-arg | Jasim Jameson | :signoff | ( Aim for success! ) | _Example use_ #+begin_example org :tangle no The sum of the first $n$ natural numbers is $\sum_{i = 0}^n i = {n × (n + 1) \over 2}$. Note that $n × (n + 1)$ is even. [[rremark:Jasim Jameson][Why are you taking about “$\mathsf{even}$” here?]] ,#+begin_rremark Bobbert Barakallah :signoff "Thank-you for pointing this out!" :color green I was trying, uh ... Yeah, to explain that ${\large n × (n + 1) \over 2}$ is always an integer. ,#+end_rremark Hence, we only need to speak about whole numbers. [[rremark:][Then please improve your transition sentences.]] #+end_example _Resulting rendition_ #+begin_quote The sum of the first $n$ natural numbers is $\sum_{i = 0}^n i = {n × (n + 1) \over 2}$. Note that $n × (n + 1)$ is even. [[rremark:Jasim Jameson][Why are you taking about “$\mathsf{even}$” here?]] #+begin_rremark Bobbert Barakallah :signoff "Thank-you for pointing this out!" :color green I was trying, uh ... Yeah, to explain that ${\large n × (n + 1) \over 2}$ is always an integer. #+end_rremark Hence, we only need to speak about whole numbers. [[rremark:][ Then please improve your transition sentences.]] #+end_quote Notice that the result contains text ---the signoff message--- that the user Jasim did not write explicitly. … Why the /stuttered/ ~rremark~? Because this package comes with a ~remark~ block that has more bells and whistles … keep reading ;-) # For the Lisp #+name: startup-code #+begin_src emacs-lisp :exports none ;; This is our 𝒳, “remark”. ;; As a link, it should be shown angry-red; ;; it takes two arguments: “color” and “signoff” ;; with default values being "red" and "". (o-defblock rremark (editor "Editor Remark" :face '(:foreground "red" :weight bold)) (color "red" signoff "") ; :please-preserve-new-lines "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}") color editor contents signoff)) ;; I don't want to change the definition, but I'd like to have ;; the following as personalised defaults for the “remark” block. ;; OR, I'd like to set this for links, which do not have argument options. (o-set-block-header-args rremark :main-arg "Jasim Jameson" :signoff "( Aim for success! )") #+end_src ** Modularity with ~thread-blockcall~ :PROPERTIES: :CUSTOM_ID: modularity_with_thread-blockcall :END: Since [[doc:o-defblock][defblock]] let's us pretend block ---and link--- types are string-valued functions, then one would expect that we can compose blocks /modularly/ as functions compose. Somewhat analogously to doc:funcall and doc:thread-last, we provide a macro [[doc:o-thread-blockcall][thread-blockcall]]. #+begin_box Example #+begin_src emacs-lisp :tangle no (thread-blockcall raw-contents (box name) (details (upcase name) :title-color "green") #+end_src = #+begin_src C :tangle no ,#+begin_details NAME :title-color "green" ,#+begin_box name contents ,#+end_box ,#+end_details #+end_src #+end_box #+html:
#+begin_details Implementation First, we need to handle the case of one block… #+begin_src emacs-lisp (cl-defmacro o--blockcall (blk &optional main-arg &rest keyword-args-then-contents) "An anaologue to `funcall` but for blocks. Usage: (blockcall blk-name main-arg even-many:key-values raw-contents) One should rarely use this directly; instead use o-thread-blockcall. " `(concat "#+end_export\n" (,(intern (format "o--%s" blk)) backend ;; defblock internal ; (format "\n#+begin_export html\n\n%s\n#+end_export\n" ,(car (last keyword-args-then-contents))) ;; contents ,@(last keyword-args-then-contents) ;; contents ,main-arg ,@(-drop-last 1 keyword-args-then-contents)) "\n#+begin_export")) #+end_src #+RESULTS: : o--blockcall Using the above sequentially does not work due to the plumbing of ~defblock~, so we handle that plumbing below … #+BEGIN_SRC emacs-lisp (defmacro o-thread-blockcall (body &rest forms) "Thread text through a number of blocks. BODY is likely to be ‘raw-contents’, possibly with user manipulations. Each FORMS is of the shape “(block-name main-argument :key-value-pairs)” (thread-blockcall x) = x (thread-blockcall x (f a)) = (blockcall f a x) (thread-blockcall x f₁ f₂) ≈ (f₂ (f₁ x)) The third is a ‘≈’, and not ‘=’, because the RHS contains ‘blockcall’s as well as massages the export matter between conseqeuctive blockcalls. A full example: (o-defblock nesting (name) nil \"Show text in a box, within details, which contains a box.\" (o-thread-blockcall raw-contents (box name) (details (upcase name) :title-color \"green\") (box (format \"⇨ %s ⇦\" name) :background-color \"blue\") )) " (if (not forms) body `(-let [result (o--blockcall ,@(car forms) ,body)] ,@(cl-loop for b in (cdr forms) collect `(setq result (o--blockcall ,@b (concat "#+begin_export\n" result "\n#+end_export" )))) result))) #+END_SRC #+RESULTS: : o-thread-blockcall #+end_details *** Short Example: /An opportunity to learn!/ :PROPERTIES: :CUSTOM_ID: Short_Example_An_opportunity_to_learn :END: The following tiny block is composed from two [[doc:o--details][details]] blocks and a [[doc:o--box][box]] block ---defined elsewhere in this article. It is intended to give the reader another opportunity to make sure they have tried to solve the puzzle posed in the main text before seeing the answer ----this works well in HTML, not so in LaTeX. #+begin_src emacs-lisp (o-defblock solution (title "Solution") (reprimand "Did you actually try? Maybe see the ‘hints’ above!" really "Solution, for real") "Show the answers to a problem, but with a reprimand in case no attempt was made." (o-thread-blockcall raw-contents (details really :title-color "red") (box reprimand :background-color "blue") (details title))) #+end_src E.g., what is 1 + 1? #+begin_spoiler ((Useless)) Hint: ((What is a number?)) #+end_spoiler #+begin_solution The answer is 2. If you're interested in such ‘fundamental’ questions, consider reading Russel and Whitehead's /Principa Mathematica/ ;-) #+end_solution #+latex: \iffalse The above box was created from: #+begin_src C :tangle no ,#+begin_solution The answer is 2. If you're interested in such ‘fundamental’ questions, consider reading Russel and Whitehead's /Principa Mathematica/ ;-) ,#+end_solution #+end_src #+latex: \fi We will make use of this block below when we get to guided problems ;-) *** Longer Example: Demonstrating Org-markup with ~org-demo~ :PROPERTIES: :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. #+begin_details Implementation #+begin_src emacs-lisp (o-defblock org-demo nil (source "Source" result "Result" source-color "cyan" result-color "cyan" style "parallel" sep (if (equal backend 'html) "@@html:


@@" "\n\n\n\n") ) "Output the CONTENTS of the block as both parsed Org and unparsed. Label the source text by SOURCE and the result text by RESULT finally, the source-result fragments can be shown in a STYLE that is either “parallel” (default) or “sequential”. SEP is the separator; e.g., a rule ‘


'. " (-let [text (concat ;; Source (thread-last raw-contents (format (if (equal backend 'html) "
%s
" "\n\\begin{verbatim}\n%s\n\\end{verbatim}")) org-export (o--blockcall box source :background-color source-color) org-export) ;; Separator sep ;; Result (thread-last raw-contents (o--blockcall box result :background-color result-color) org-export))] (if (equal style "parallel") (o--blockcall parallel "2" :bar nil text) (concat "#+end_export\n" text "\n#+begin_export")))) #+end_src #+RESULTS: | :export | (lambda (label description backend) (s-replace-all `((@@ . )) (o--org-demo backend (or description label) label :o-link? t))) | :help-echo | (lambda (window object position) (save-excursion (goto-char position) (-let* (((&plist :path :format :raw-link :contents-begin :contents-end) (cadr (org-element-context))) (description (when (equal format 'bracket) (copy-region-as-kill contents-begin contents-end) (substring-no-properties (car kill-ring))))) (format %s | #+end_details #+html:
#+begin_box Example #+begin_parallel :bar t _This_ #+begin_example org ,#+begin_org-demo /italics/ and _underline_ $e^{i \times \pi} + 1 = 0$ ,#+end_org-demo #+end_example #+html:

_Yields_ #+begin_org-demo /italics/ and _underline_ $e^{i \times \pi} + 1 = 0$ #+end_org-demo #+end_parallel #+end_box #+html:
#+begin_box (Sequential) Example #+begin_parallel :bar t _This_ #+begin_example org ,#+begin_org-demo :style seq /italics/ and _underline_ $e^{i \times \pi} + 1 = 0$ ,#+end_org-demo #+end_example #+html:






_Yields_ #+begin_org-demo :style seq /italics/ and _underline_ $e^{i \times \pi} + 1 = 0$ #+end_org-demo #+end_parallel #+end_box However, since our implementation scheme relies on a preprocessing step before export, we cannot use ~org-demo~ to show the results of special blocks: They disappear in the preprocessing step! #+begin_parallel :bar t E.g., this #+begin_example org ,#+begin_org-demo ,#+begin_box There is no special block ‘box’ to touch! ,#+end_box ,#+end_org-demo #+end_example #+html:



yields the /mess/ #+begin_org-demo #+begin_box There is no special block ‘box’ to touch! #+end_box #+end_org-demo #+end_parallel However, it does work with links! #+begin_org-demo [[box:][Box-as-link! Boxception!]] #+end_org-demo #+latex: \fi ** Practice Problems: /Now you try!/ :PROPERTIES: :CUSTOM_ID: practice_problems :END: *[[green:][A 5-page PDF covering ELisp fundamentals]]* can be found *[[https://alhassy.github.io/ElispCheatSheet/CheatSheet.pdf][here]]*. The first problem is to /get you going with Lisp/, the next two are actually useful blocks. The [[doc:o--rename][rename]] is useful for when you want to change some names or translate some words; [[doc:o--spoiler][spoiler]] is useful when we want to test a student's understanding, or to subtly hide the answer to a puzzle so the reader has the opportunity to attempt solving it. *** Sttutttterrr :PROPERTIES: :CUSTOM_ID: Sttutttterrr :END: Define a block [[doc:o--stutter][stutter]] so that the following examples behave as shown. #+begin_details Hints 1. You need at-most 5 lines of Lisp. 2. These functions /may/ be useful: doc:s-repeat, doc:numberp, doc:string-to-number #+end_details #+html:
#+begin_box Examples #+begin_parallel :bar t The following outputs, well, nothing, since we asked for zero repetitions. #+begin_src org :tangle no ,#+begin_stutter 0 words more words ,#+end_stutter #+end_src #+begin_stutter 0 Body0 Body1 #+end_stutter #+columnbreak: In contrast … #+begin_org-demo [[stutter:5][woah, I'm repeated 5 times!]] #+end_org-demo #+end_parallel #+end_box #+html:
#+begin_solution #+begin_src emacs-lisp (o-defblock stutter (reps 2) nil "Output the CONTENTS of the block REPS many times" (-let [num (if (numberp reps) reps (string-to-number reps))] (s-repeat num contents))) #+end_src #+RESULTS: #+end_solution *** Textual Substitution ---A translation tool :PROPERTIES: :CUSTOM_ID: Textual_Substitution :END: Define a block [[doc:o--rename][rename]] so that the following examples behave as shown. #+begin_details Hints 1. It can be done in less than 10 lines of Lisp. 2. First, try to doc:s-replace-all the substitution ~'(("Allah" . "God") ("Yacoub". "Jacob") ("Yusuf" . "Joseph"))~ only. 3. Then take out such hard-coded substitutions … these functions /may/ be helpful: doc:--map / doc:-map, doc:s-split, doc:s-trim #+end_details #+html:
#+begin_box Examples # This… #+begin_example org ,#+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 #+end_example Yields… #+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 -------------------------------------------------------------------------------- #+begin_org-demo :style sequential [[rename:Pharaoh to Firaun, Joseph to Yusuf][Genesis 41-17: Pharaoh said unto Joseph, /In my dream, behold, I stood upon the bank of the river/ …]] #+end_org-demo #+end_box #+html:
#+begin_solution #+begin_src emacs-lisp (o-defblock rename (list "") nil "Perform the given LIST of substitutions on the text. The LIST is a comma separated list of ‘to’ separated symbols. In a link, no quotes are needed." (s-replace-all (--map (cons (car it) (cadr it)) (--map (s-split " to " (s-trim it)) (s-split "," list))) contents)) #+end_src #+RESULTS: #+end_solution *** Spoilers! ---“fill in the blanks” :PROPERTIES: :CUSTOM_ID: spoilers :END: # Warning: Enabling the following ruins the beautiful kbd:𝒳 styling, upon export. # +html_head: " id color color id) (s-replace-regexp (concat (regexp-quote left) "\\(.*?\\)" (regexp-quote right)) (format "@@html: \\1 @@" id) contents))))) #+end_src #+RESULTS: | :export | (lambda (label description backend) (s-replace-all `((@@ . )) (o--spoiler backend (or description label) label :o-link? t))) | :help-echo | (lambda (window object position) (save-excursion (goto-char position) (-let* (((&plist :path :format :raw-link :contents-begin :contents-end) (cadr (org-element-context))) (description (when (equal format 'bracket) (copy-region-as-kill contents-begin contents-end) (substring-no-properties (car kill-ring))))) (format %s | There's actually a problem with this ‘solution’; can you find it? #+begin_spoiler Hint: ((Try the link form and see how it breaks!)) #+end_spoiler #+end_solution :OnMouseOver_OnMouseOut_Approach: An alternative would be to use JS with mouse parameters; e.g.: #+begin_export html Text #+end_export Possibly using ID's as the current working solution above. :End: *** Judgements: Inference rules and proof trees :PROPERTIES: :CUSTOM_ID: Judgements-Inference-rules-and-proof-trees :END: Using a mixture of ~\frac~ and ~\displaystyle~, define a block [[doc:o--tree][tree]] so that the following examples behave as shown. Hint: doc:org-list-to-lisp and doc:with-temp-buffer may be useful ;-) #+begin_box Programming ≈ Proving #+begin_parallel *Source* #+begin_src org :tangle no ,#+begin_tree + Function Application :: f(a) : B - a : A - f : A → B + Modus Ponens :: q - p - p ⇒ q ,#+end_tree #+end_src # +columnbreak: #+html:

*Result* #+begin_tree + Function Application :: f(a) : B - a : A - f : A → B + Modus Ponens :: q - p - p ⇒ q #+end_tree #+end_parallel #+end_box #+begin_solution #+begin_src emacs-lisp (defun o--list-to-math (lst) "Get a result LST from ORG-LIST-TO-LISP and render it as a proof tree." (cond ((symbolp lst) "") ((symbolp (car lst)) (o--list-to-math (cadr lst))) (t (-let* (((conclusion₀ children) lst) ((name named?) (s-split " :: " conclusion₀)) (conclusion (or named? conclusion₀))) (if (not children) (if named? (format "\\frac{}{%s}[%s]" conclusion name) conclusion) (format "\\frac{\\displaystyle %s}{%s}%s" (s-join " \\qquad " (mapcar #'o--list-to-math children)) conclusion (if named? (format "[\\text{%s}]" name) ""))))))) (o-defblock tree (main-arg) nil "Write a proof tree using Org-lists. To get premises₀ … premisesₙ ────────────────────────────[ reason ] conclusion You type ,#+begin_tree + reason :: conclusion - premises₀ - premises₁ ⋮ - premisesₙ ,#+end_tree Where each premisesᵢ may, recursively, also have named reasons and (indented) child premises of its own. If there are multiple trees, they are shown one after the other. The text in this block should be considered LaTeX; as such, Org markup is not recognised. A proof tree, derivation, is then just a deeply nested itemisation. For instance, assuming P = Q(X), X = Y, Q(Y) = R, the following proves P = R. ,#+begin_tree + Trans :: P = R - P = Q(X) + ✓ - Trans :: Q(X) = R + Trans :: Q(X) = Q(Y) - Refl :: Q(X) = Q(X) + ✓ - Leibniz :: Q(X) = Q(Y) + X = Y - ✓ + Sym :: Q(Y) = R - R = Q(Y) - ✓ ,#+end_tree" (s-join "" (--map (format "\\[%s\\]" (o--list-to-math it)) (cdr (with-temp-buffer (insert raw-contents) (goto-char (point-min)) (org-list-to-lisp)))))) #+end_src #+RESULTS: | :export | (lambda (label description backend) (s-replace-all `((@@ . )) (o--tree backend (or description label) label :o-link? t))) | :help-echo | (lambda (window object position) (save-excursion (goto-char position) (-let* (((&plist :path :format :raw-link :contents-begin :contents-end) (cadr (org-element-context))) (description (when (equal format 'bracket) (copy-region-as-kill contents-begin contents-end) (substring-no-properties (car kill-ring))))) (format %s | #+end_solution For more on these ‘proof trees’, see [[https://cpb-us-w2.wpmucdn.com/u.osu.edu/dist/a/4597/files/2014/08/Natural_Logic-2epb48e.pdf][‘Natural Logic’ by Neil Tennant]]. # Out of print, but downloadable as a scanned PDF as the above link. \[\] (*/Warning!/* For MathJax to activate, you should have some math ~$...$~ somewhere /besides/ the ~tree~ blocks; just ~\[\]~ suffices. ) ** What's the rest of this article about? :PROPERTIES: :CUSTOM_ID: Whats_the_rest_of_this_article_about? :END: The rest of the article showcases the special blocks declared with ~defblock~ to allow the above presentation ---with folded regions, coloured boxes, tooltips, parallel columns of text, etc. Enjoy ;-) ** COMMENT The Older =o--𝒳= Utility :PROPERTIES: :CUSTOM_ID: Core-Utility :END: For posterity, below is the original route taken to solve the same problem. In particular, the route outlined below /may/ be faster. Why is ~defblock~ better? - The approach below requires an awkward way to handle arguments, key-values. - It requires the user to learn a new interface. - Even if it's slower, ~defblock~ uses a very familiar interface and requires less Lisp mastery on the user's part. -------------------------------------------------------------------------------- :Hide: #+BEGIN_SRC emacs-lisp ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Core utility ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; #+END_SRC :End: The simplest route is to ‘advise’ ---i.e., function patch or overload--- the Org export utility for special blocks to consider calling a method =o--𝒳= whenever it encounters a special block named =𝒳=. #+BEGIN_SRC emacs-lisp :noweb-ref enable-mode :tangle no (advice-add #'org-html-special-block :before-until (apply-partially #'o--advice 'html)) (advice-add #'org-latex-special-block :before-until (apply-partially #'o--advice 'latex)) #+END_SRC #+RESULTS: Here is the actual advice: #+BEGIN_SRC emacs-lisp (defun o--advice (backend blk contents _) "Invoke the appropriate custom block handler, if any. A given custom block BLK has a TYPE extracted from it, then we send the block CONTENTS along with the current export BACKEND to the formatting function O--TYPE if it is defined, otherwise, we leave the CONTENTS of the block as is. We also support the seemingly useless blocks that have no contents at all, not even an empty new line." (let* ((type (nth 1 (nth 1 blk))) (handler (intern (format "o--%s" type)))) (ignore-errors (apply handler backend (or contents "") nil)))) #+END_SRC #+RESULTS: : #+latex: \noindent *To support a new block named 𝒳:* 1. Define a function =o--𝒳=. 2. It must take two arguments: - ~backend~ ⇒ A symbol such as ='html= or ='latex=, - ~content~ ⇒ The string contents of the special block. 3. The function must return a string, possibly depending on the backend being exported to. The resulting string is inserted literally in the exported file. 4. Test out your function as in =(o--𝒳 'html "some input")= ---this is a quick way to find errors. 5. Enjoy ^_^ #+begin_center If no such function is defined, we export =𝒳= blocks using the default mechanism, as discussed earlier, as a LaTeX environment or an HTML =div=. #+end_center #+latex: \noindent An example is provided at the end of this section. #+latex: \noindent Of-course, when the user disables our mode, then we remove such advice. #+BEGIN_SRC emacs-lisp :noweb-ref disable-mode :tangle no (advice-remove #'org-html-special-block (apply-partially #'o--advice 'html)) (advice-remove #'org-latex-special-block (apply-partially #'o--advice 'latex)) #+END_SRC #+RESULTS: ***   =:argument:= Extraction :PROPERTIES: :CUSTOM_ID: argument-Extraction :END: As far as I can tell, there is no way to provide arguments to special blocks. As such, the following utility looks for lines of the form =:argument: value= within the contents of a block and returns an updated contents string that no longer has such lines followed by an association list of such argument-value pairs. #+BEGIN_SRC emacs-lisp (defun o--extract-arguments (contents &rest args) "Get list of CONTENTS string with ARGS lines stripped out and values of ARGS. Example usage: (-let [(contents′ . (&alist 'k₀ … 'kₙ)) (…extract-arguments contents 'k₀ … 'kₙ)] body) Within ‘body’, each ‘kᵢ’ refers to the ‘value’ of argument ‘:kᵢ:’ in the CONTENTS text and ‘contents′’ is CONTENTS with all ‘:kᵢ:’ lines stripped out. + If ‘:k:’ is not an argument in CONTENTS, then it is assigned value NIL. + If ‘:k:’ is an argument in CONTENTS but is not given a value in CONTENTS, then it has value the empty string." (let ((ctnts contents) (values (cl-loop for a in args for regex = (format ":%s:\\(.*\\)" a) for v = (cadr (s-match regex contents)) collect (cons a v)))) (cl-loop for a in args for regex = (format ":%s:\\(.*\\)" a) do (setq ctnts (s-replace-regexp regex "" ctnts))) (cons ctnts values))) #+END_SRC #+RESULTS: : o--extract-arguments For example, we use this feature to indicate when a column break should happen in a =parallel= block and which person is making editorial remarks in an =remark= block. Why the =:𝒳:= notation? At the start of a line, a string of this form is coloured ---I don't recall why that is--- and that's a good enough reason to make use of such an existing support. #+begin_remark Aside In org-mode, ‘drawers’ are pieces of text that begin with =:my_drawer_name:= on a line by itself and end with =:end:= on a line by itself, and these delimiters allow us to fold away such regions and possibly exclude them from export. That is, drawers act as a light-weight form of blocks. Anyhow, Org colours drawer delimiters, #+end_remark *** An Example Special Block ---=foo= :PROPERTIES: :CUSTOM_ID: COMMENT-An-Example-Special-Block-foo :END: Herein we show an example function =o--𝒳= that makes use of arguments. In a so-called =foo= block, all occurrences of the word =foo= are replaced by =bar= unless the argument =:replacement:= is given a value. [[file:images/foo_block.png]] #+name: startup-code #+begin_src emacs-lisp :tangle no (defun o--foo (backend contents) "The FOO block type replaces all occurances of ‘foo’ with ‘bar’, unless a ‘:replacement:’ is provided." (-let [(contents′ . (&alist 'replacement)) (o--extract-arguments contents 'replacement)] (s-replace "foo" (or replacement "bar") contents′))) #+end_src :Outdated_hide: Here's an example usage: #+begin_parallel #+begin_example org #+begin_foo :replacement: woah I am foo; Indeed FoO is what I fOo! #+end_foo #+end_example :columnbreak: #+begin_foo :replacement: woah I am foo; Indeed FoO is what I fOo! #+end_foo #+end_parallel # See the implementation matter of ~edcomm~ or ~parallel~ for a more involved definition # that behaves differently depending on the export backend. :End: * COMMENT refactor defblock signature :way_in_future:someday: :PROPERTIES: :CUSTOM_ID: COMMENT-refactor-defblock-signature :END: (defblock name args &rest more) + args ⇒ (main-arg default-value keys-and-values) + more ⇒ (optional-link-face-setup optional-newline-decl- optional-docstring actual-body) * Editor Comments :PROPERTIES: :CUSTOM_ID: editor-comments :END: “Editor Comments” are intended to be top-level first-class comments in an article that are inline with the surrounding text and are delimited in such a way that they are visible but drawing attention. I first learned about this idea from Wolfram Kahl ---who introduced me to Emacs many years ago. We # implement editor comments as special blocks named [[doc:o--remark][remark]]. #+begin_box Example #+begin_parallel _This_ #+begin_src org :tangle no :tangle no In LaTeX, a =remark= appears inline with the text surrounding it. ,#+begin_remark Bobert org-mode is dope, yo! ,#+replacewith: Org-mode is essentially a path toward enlightenment. ,#+end_remark Unfortunately, in the HTML rendition, the =remark= is its own paragraph and thus separated by new lines from its surrounding text. #+end_src #+html:


_Yields_ In LaTeX, an =remark= appears inline with the text surrounding it. #+begin_remark Bobert org-mode is dope, yo! #+replacewith: Org-mode is essentially a path toward enlightenment. #+end_remark Unfortunately, in the HTML rendition, the =remark= is its own paragraph and thus separated by new lines from its surrounding text. #+end_parallel #+end_box :Pics_old: #+caption: In order: Chrome, Emacs Web Wowser, Org source, PDF [[file:images/edcomm.png]] :End: # | /Any new ---possibly empty--- inner lines in the =remark= are desirably preserved/ | -------------------------------------------------------------------------------- #+begin_details "Implementing ‘remark’, from §ection 1, with more bells and whistles" :title-color pink #+BEGIN_SRC emacs-lisp (defvar o-hide-editor-comments nil "Should editor comments be shown in the output or not.") (o-defblock remark (editor "Editor Remark" :face '(:foreground "red" :weight bold)) (color "black" signoff "" strong nil) ; :inline-please__see_margin_block_for_a_similar_incantation ; ⇒ crashes! "Format CONTENTS as an first-class editor comment according to BACKEND. The CONTENTS string has an optional switch: If it contains a line with having only ‘#+replacewith:’, then the text preceding this clause should be replaced by the text after it; i.e., this is what the EDITOR (the person editing) intends and so we fromat the replacement instruction (to the authour) as such. In Emacs, as links, editor remarks are shown with a bold red; but the exported COLOR of a remark is black by default and it is not STRONG ---i.e., bold---. There is an optional SIGNOFF message that is appended to the remark. " (-let* (;; Are we in the html backend? (tex? (equal backend 'latex)) ;; fancy display style (boxed (lambda (x) (if tex? (concat "\\fbox{\\bf " x "}") (concat "" "" x "")))) ;; Is this a replacement clause? ((this that) (s-split "\\#\\+replacewith:" contents)) (replacement-clause? that) ;; There is a ‘that’ (replace-keyword (if tex? "\\underline{Replace:}" " Replace:")) (with-keyword (if tex? "\\underline{With:}" "With:" )) (editor (format "[%s:%s" editor (if replacement-clause? replace-keyword ""))) (contents′ (if replacement-clause? (format "%s %s %s" this (org-export (funcall boxed with-keyword)) that) contents)) ;; “[Editor Comment:” (edcomm-begin (funcall boxed editor)) ;; “]” (edcomm-end (funcall boxed "]"))) (setq org-export-allow-bind-keywords t) ;; So users can use “#+bind” immediately (if o-hide-editor-comments "" (format (pcase backend ('latex (format "{\\color{%%s}%s %%s %%s %%s %%s}" (if strong "\\bfseries" ""))) (_ (format "<%s style=\"color: %%s;\">%%s %%s %%s %%s" (if strong "strong" "p") (if strong "strong" "p")))) color edcomm-begin contents′ signoff edcomm-end)))) #+END_SRC #+RESULTS: | :face | (:foreground red :weight bold) | :export | (lambda (label description backend) (s-replace-all `((@@ . )) (o--remark backend (or description label) label :o-link? t))) | :help-echo | (lambda (window object position) (save-excursion (goto-char position) (-let* (((&plist :path :format :raw-link :contents-begin :contents-end) (cadr (org-element-context))) (description (when (equal format 'bracket) (copy-region-as-kill contents-begin contents-end) (substring-no-properties (car kill-ring))))) (format %s | :Older_version: #+BEGIN_SRC emacs-lisp :tangle no (defvar o-hide-editor-comments nil "Should editor comments be shown in the output or not.") (defun o--edcomm (backend contents) "Format CONTENTS as an first-class editor comment according to BACKEND. The CONTENTS string has two optional argument switches: 1. :ed: ⇒ To declare an editor of the comment. 2. :replacewith: ⇒ [Nullary] The text preceding this clause should be replaced by the text after it." (-let* ( ;; Get arguments ((contents₁ . (&alist 'ed)) (o--extract-arguments contents 'ed)) ;; Strip out any

tags (_ (setq contents₁ (s-replace-regexp "

" "" contents₁))) (_ (setq contents₁ (s-replace-regexp "

" "" contents₁))) ;; Are we in the html backend? (html? (equal backend 'html)) ;; fancy display style (boxed (lambda (x) (if html? (concat "" "" x "") (concat "\\fbox{\\bf " x "}")))) ;; Is this a replacement clause? ((this that) (s-split ":replacewith:" contents₁)) (replacement-clause? that) ;; There is a ‘that’ (replace-keyword (if html? " Replace:" "\\underline{Replace:}")) (with-keyword (if html? "With:" "\\underline{With:}")) (editor (format "[%s:%s" (if (s-blank? ed) "Editor Comment" ed) (if replacement-clause? replace-keyword ""))) (contents₂ (if replacement-clause? (format "%s %s %s" this (funcall boxed with-keyword) that) contents₁)) ;; “[Editor Comment:” (edcomm-begin (funcall boxed editor)) ;; “]” (edcomm-end (funcall boxed "]"))) (setq org-export-allow-bind-keywords t) ;; So users can use “#+bind” immediately (if o-hide-editor-comments "" (format (pcase backend ('latex "%s %s %s") (_ "

%s %s %s

")) edcomm-begin contents₂ edcomm-end)))) #+END_SRC :End: In the HTML export, the =edcomm= special block is /not/ in-line with the text surrounding it ---ideally, it would be inline so that existing paragraphs are not split into multiple paragraphs but instead have an editor's comment indicating suggested alterations. Let's have some sanity tests... #+begin_src emacs-lisp :tangle tests.el :comments link (deftest "The user's remark is enclosed in the default delimiters" [remark] (⇝ (⟰ "#+begin_remark Here is some meta-commentary... ,#+end_remark") (* anything) "[Editor Remark:" (* anything) "Here is some meta-commentary" (* anything) "]")) ;; The other features of remark blocks should be tested; ;; but this is not a pressing, nor interesting, concern. #+end_src #+end_details -------------------------------------------------------------------------------- #+begin_details Example: No optional arguments #+begin_remark /Please/ *change* _this_ section to be more, ya know, professional. #+end_remark -------------------------------------------------------------------------------- *Source:* #+begin_src org :tangle no ,#+begin_remark /Please/ *change* _this_ section to be more, ya know, professional. ,#+end_remark #+end_src #+end_details #+begin_details "Example: Only providing a main argument ---i.e., the remark author, the editor" #+begin_remark Bobert /Please/ *change* _this_ section to be more, ya know, professional. #+end_remark #+latex: \vspace{1em}\noindent -------------------------------------------------------------------------------- *Source:* #+begin_src org :tangle no ,#+begin_remark Bobert /Please/ *change* _this_ section to be more, ya know, professional. ,#+end_remark #+end_src #+end_details #+begin_details Example: Possibly with no contents: #+begin_remark Bobert #+end_remark -------------------------------------------------------------------------------- *Source:* #+begin_src org :tangle no ,#+begin_remark Bobert ,#+end_remark #+end_src #+end_details #+begin_details "Example: Empty contents, no authour, nothing" #+begin_remark #+end_remark -------------------------------------------------------------------------------- *Source:* #+begin_src org :tangle no ,#+begin_remark ,#+end_remark #+end_src #+end_details #+latex: \vspace{1em}\noindent #+begin_details Example: Possibly with an empty new line #+begin_remark #+end_remark -------------------------------------------------------------------------------- *Source:* #+begin_src org :tangle no ,#+begin_remark ,#+end_remark #+end_src #+end_details #+latex: \iffalse #+begin_details "Example: With a “#+replacewith:” clause" #+begin_remark The two-dimensional notation; e.g., $\sum_{i = 0}^n i^2$ #+replacewith: A linear one-dimensional notation; e.g., $(\Sigma i : 0..n \;\bullet\; i^2)$ #+end_remark -------------------------------------------------------------------------------- *Source:* #+begin_src org :tangle no ,#+begin_remark The two-dimensional notation; e.g., $\sum_{i = 0}^n i^2$ ,#+replacewith: A linear one-dimensional notation; e.g., $(\Sigma i : 0..n \;\bullet\; i^2)$ ,#+end_remark #+end_src #+end_details #+latex: \fi #+latex: \vspace{1em}\noindent #+begin_details Example: Possibly “malformed” replacement clauses Forgot the thing to be replaced… #+begin_remark #+replacewith: A linear one-dimensional notation; e.g., $(\Sigma i : 0..n \;\bullet\; i^2)$ #+end_remark -------------------------------------------------------------------------------- Forgot the new replacement thing… #+begin_remark The two-dimensional notation; e.g., $\sum_{i = 0}^n i^2$ #+replacewith: #+end_remark -------------------------------------------------------------------------------- Completely lost one's train of thought… #+begin_parallel #+begin_remark #+replacewith: #+end_remark #+columnbreak: *Source:* #+begin_src org :tangle no ,#+begin_remark ,#+replacewith: ,#+end_remark #+end_src #+end_parallel #+end_details -------------------------------------------------------------------------------- A block to make an editorial comment could be overkill in some cases; luckily [[doc:o-defblock][defblock]] automatically provides an associated link type for the declared special blocks. - Syntax: =[[remark:person_name][editorial remark]]=. - This link type exports the same as the =remark= block type; however, in Emacs it is shown with an ‘angry’ ---bold--- red face. :Old_unnecessary_implementaiton: #+begin_src emacs-lisp -n -r (org-link-set-parameters "edcomm" :follow (lambda (_)) :export (lambda (label description backend) (o--edcomm backend (format ":ed:%s\n%s" label description))) :help-echo (lambda (_ __ position) (save-excursion (goto-char position) (-let [(&plist :path) (cadr (org-element-context))] (format "%s made this remark" (s-upcase path))))) :face '(:foreground "red" :weight bold)) #+end_src :End: #+begin_box Example: Terse remarks via links #+begin_parallel :bar t ~[[edcomm:Jasim][Hello, where are you?]]~ # +html:
[[remark:Jasim][Hello, where are you?]] #+end_parallel ------- #+begin_parallel :bar t The =#+replacewith:= switch ---and usual Org markup--- also works with these links: @@html:
@@ ~[[remark:Qasim][/‘j’/ #+replacewith: /‘q’/]]~ #+html:
[[remark:Qasim][/‘j’/ #+replacewith: /‘q’/]] #+end_parallel #+end_box -------------------------------------------------------------------------------- All editor comments, remarks, are disabled by declaring, in your Org file: #+begin_example org ,#+bind: o-hide-editor-comments t #+end_example The =#+bind:= keyword makes Emacs variables buffer-local during export ---it is evaluated /after/ any =src= blocks. To use it, one must declare in their Emacs init file the following line, which our mode ensures is true. #+BEGIN_SRC emacs-lisp :tangle no :noweb-ref enable-mode (setq org-export-allow-bind-keywords t) #+END_SRC | ( Remember to =C-c C-c= the =#+bind= to activate it, the first time it is written. ) | #+bind: o-hide-editor-comments nil * Folded Details ---As well as boxed text and subtle colours :PROPERTIES: :CUSTOM_ID: Folded-Details :END: #+begin_center /How did we fold away those implementations?/ #+end_center Sometimes there is a remark or a code snippet that is useful to have, but not relevant to the discussion at hand and so we want to /fold away such [[doc:o--details][details]]/. + ‘Conversation-style’ articles, where the author asks the reader questions whose answers are “folded away” so the reader can think about the exercise before seeing the answer. + Hiding boring but important code snippets, such as a list of import declarations or a tedious implementation. #+latex_header: \usepackage{tcolorbox} #+begin_center Requires: src_latex[:exports code]{,#+latex_header: \usepackage{tcolorbox}} #+end_center :Pics: #+caption: Visually hiding, folding away, details [[file:images/details.png]] :End: #+begin_details Implementation #+begin_src emacs-lisp (o-defblock details (title "Details") (title-color "green") "Enclose contents in a folded up box, for HTML. For LaTeX, this is just a boring, but centered, box. By default, the TITLE of such blocks is “Details” and its TITLE-COLOR is green. In HTML, we show folded, details, regions with a nice greenish colour. In the future ---i.e., when I have time--- it may be prudent to expose more aspects as arguments, such as ‘background-color’. " (format (pcase backend (`latex "\\begin{quote} \\begin{tcolorbox}[colback=%s,title={%s},sharp corners,boxrule=0.4pt] %s \\end{tcolorbox} \\end{quote}") (_ "
%s %s
")) title-color title contents)) #+end_src #+RESULTS: | :export | (lambda (label description backend) (s-replace-all `((#+end_export . ) (,(format #+begin_export %s backend) . )) (o--details backend (or description label) label))) | :help-echo | (lambda (window object position) (save-excursion (goto-char position) (-let* (((&plist :path :format :raw-link :contents-begin :contents-end) (cadr (org-element-context))) (description (when (equal format 'bracket) (copy-region-as-kill contents-begin contents-end) (substring-no-properties (car kill-ring))))) (format %s | :HACK:Disabled: Above is a lower-case ‘d’etails block, we now make a captial-case ‘D’etails block, for the not-too-odd situation we want to have, right away, one details block within another. #+begin_src emacs-lisp :exports none (o-defblock Details (title "Details") (title-color "green") "Enclose contents in a folded up box, for HTML. For LaTeX, this is just a boring, but centered, box. By default, the TITLE of such blocks is “Details” and its TITLE-COLOR is green. In HTML, we show folded, details, regions with a nice greenish colour. In the future ---i.e., when I have time--- it may be prudent to expose more aspects as arguments, such as ‘background-color’. " (format (pcase backend (`latex "\\begin{quote} \\begin{tcolorbox}[colback=%s,title={%s},sharp corners,boxrule=0.4pt] %s \\end{tcolorbox} \\end{quote}") (_ "
%s %s
")) title-color title contents)) #+end_src MA: TODO: The above should not be present, it should instead be factored out into a defblock-alias function. :End: :Posterity_Older_implementation: #+BEGIN_SRC emacs-lisp -n -r :tangle no (defun o--details (backend contents) "Format CONTENTS as a ‘folded region’ according to BACKEND. CONTENTS may have a ‘:title’ argument specifying a title for the folded region." (-let* (;; Get arguments ((contents′ . (&alist 'title)) (o--extract-arguments contents 'title))) (when (s-blank? title) (setq title "Details")) (setq title (s-trim title)) (format (s-collapse-whitespace ;; Remove the whitespace only in the nicely presented ;; strings below (pcase backend (`html "
%s %s
") (`latex "\\begin{quote} \\begin{tcolorbox}[colback=white,sharp corners,boxrule=0.4pt] \\textbf{%s:} %s \\end{tcolorbox} \\end{quote}"))) title contents′))) #+END_SRC #+RESULTS: : o--details :End: We could use src_latex[:exports code]{\begin{quote}\fbox{\parbox{\linewidth}{\textbf{Details:} ...}}\end{quote}}; however, this does not work well with [[https://github.com/alhassy/emacs.d#bibliography--coloured-latex-using-minted][minted]] for coloured source blocks. Instead, we use ~tcolorbox~. Let's have some sanity tests... #+begin_src emacs-lisp :tangle tests.el :comments link (deftest "The result is a
tag containing the user's title & text." [details] (⇝ (⟰ "#+begin_details TITLE-RIGHT-HERE My aside... ,#+end_details") "")) #+end_src #+end_details If you fear that your readers may not click on details boxes in the HTML export, you could, say, have the details heading “flash pink” whenever the user hovers over it. This can be accomplished by inserting the following incantation into your Org file or place it into your ~org-html-head~ variable. #+begin_example org #+html: #+end_example # Which, I will do, for fun. #+html: ** Example: /Here's a nifty puzzle, can you figure it out?/ :PROPERTIES: :CUSTOM_ID: Example :END: Reductions ---incidentally also called ‘folds’[fn:1]--- embody primitive recursion and thus computability. For example, what does the following compute when given a whole number 𝓃? #+BEGIN_SRC emacs-lisp :tangle no (-reduce #'/ (number-sequence 1.0 𝓃)) #+END_SRC #+begin_details Solution :title-color pink Rather than guess-then-check, let's /calculate/! #+begin_src emacs-lisp :tangle no (-reduce #'/ (number-sequence 1.0 𝓃)) = ;; Lisp is strict: Evaluate inner-most expression (-reduce #'/ '(1.0 2.0 3.0 … 𝓃)) = ;; Evaluate left-associating reduction (/ (/ (/ 1.0 2.0) ⋯) 𝓃) =;; Arithmetic: (/ (/ a b) c) = (* (/ a b) (/ 1 c)) = (/ a (* b c)) (/ 1.0 (* 2.0 3.0 … 𝓃)) #+END_SRC We have thus found the above Lisp program to compute the inverse factorial of 𝓃; i.e., $\large \frac{1}{𝓃!}$. #+end_details Neato, let's do more super cool stuff ^_^ #+begin_footnotesize ( In the Emacs Web Wowser, folded regions are displayed unfolded similar to LaTeX. ) #+end_footnotesize ** Boxed Text :PROPERTIES: :CUSTOM_ID: Boxed-Text :END: Folded regions, as implemented above, are displayed in a super neat text box which may be useful to enclose text to make it standout ---without having it folded away. As such, we provide the special block [[doc:o--box][box]] to enclosing text in boxes. #+latex_header: \usepackage{tcolorbox} #+begin_center Requires: src_latex[:exports code]{#+latex_header: \usepackage{tcolorbox}} #+end_center #+begin_details Implementation #+begin_src emacs-lisp (o-defblock box (title "") (background-color nil) "Enclose text in a box, possibly with a title. By default, the box's COLOR is green for HTML and red for LaTeX, and it has no TITLE. The HTML export uses a padded div, whereas the LaTeX export requires the tcolorbox pacakge. In the future, I will likely expose more arguments. " (apply #'concat (pcase backend (`latex `("\\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}")) (_ `("
" "

" ,title "

" ,contents "
"))))) #+end_src #+RESULTS: | :export | (lambda (label description backend) (s-replace-all `((#+end_export . ) (,(format #+begin_export %s backend) . )) (o--box backend (or description label) label))) | :help-echo | (lambda (window object position) (save-excursion (goto-char position) (-let* (((&plist :path :format :raw-link :contents-begin :contents-end) (cadr (org-element-context))) (description (when (equal format 'bracket) (copy-region-as-kill contents-begin contents-end) (substring-no-properties (car kill-ring))))) (format %s | Let's have some sanity tests... #+begin_src emacs-lisp :tangle tests.el (deftest "We have an HTML box enclosing the user's title (in Pay Attention!" (* anything) "This is the key insight" (* anything))) #+end_src #+end_details #+latex_header: \newunicodechar{ᵒ}{\ensuremath{{}^o}} #+html:
#+begin_box Example: A super boring observation presented obscurely :background-color blue If you start walking ---say, counterclockwise--- along the unit circle from its right-most point and walk 180ᵒ, then you will be at its left-most point. That is, \[ e^{i · \pi} \;=\; - 1 \] #+end_box /How did we get that nice light blue? What is its HTML code?/ That's not something I care to remember, so let's make a handy dandy utility … Now when users request a colour to ~box~ their text, it will be a ‘subtle colour’ ;-) #+begin_details Implementation for Subtle Colours #+begin_src emacs-lisp -r -n (defun o-subtle-colors (c) "HTML codes for common colours. Names are very rough approximates. Translations from: https://www.december.com/html/spec/softhues.html" (pcase c ("teal" "#99FFCC") ;; close to aqua ("brown" "#CCCC99") ;; close to moss ("gray" "#CCCCCC") ("purple" "#CCCCFF") ("lime" "#CCFF99") ;; brighter than ‘green’ ("green" "#CCFFCC") ("blue" "#CCFFFF") ("orange" "#FFCC99") ("peach" "#FFCCCC") ("pink" "#FFCCFF") ("yellow" "#FFFF99") ("custard" "#FFFFCC") ;; paler than ‘yellow’ (c c) )) #+end_src #+latex: \iffalse To use these colour names, you will need the following incantations in your Org file. #+begin_org-demo #+latex_header: \usepackage{xcolor} #+latex_header: \definecolor{teal} {HTML}{99FFCC} #+latex_header: \definecolor{brown} {HTML}{CCCC99} #+latex_header: \definecolor{gray} {HTML}{CCCCCC} #+latex_header: \definecolor{purple} {HTML}{CCCCFF} #+latex_header: \definecolor{lime} {HTML}{CCFF99} #+latex_header: \definecolor{green} {HTML}{CCFFCC} #+latex_header: \definecolor{blue} {HTML}{CCFFFF} #+latex_header: \definecolor{orange} {HTML}{FFCC99} #+latex_header: \definecolor{peach} {HTML}{FFCCCC} #+latex_header: \definecolor{pink}{HTML}{FFCCFF} #+latex_header: \definecolor{yellow} {HTML}{FFFF99} #+latex_header: \definecolor{custard}{HTML}{FFFFCC} #+latex_header: \definecolor{cyan}{HTML}{00FFFF} #+end_org-demo #+latex_header: \definecolor{"cyan"}{HTML}{00FFFF} In the future, it'd be nice to account for colours for LaTeX as well. ( E.g., src_latex[:exports code]{\color{blue}} is a nightmare. ) #+latex: \fi #+end_details It may be useful to /fuse/ the ~box~ and ~details~ concepts into one. Something for future me ---or another contributor--- to think about ;-) * Parallel :PROPERTIES: :CUSTOM_ID: Parallel :END: Articles can get lengthy when vertical whitespace is wasted on thin lines; instead, one could save space by using /[[doc:o--parallel][parallel]] columns of text/. #+latex_header: \usepackage{multicol} #+begin_center Requires: src_latex[:exports code]{#+latex_header: \usepackage{multicol}} #+end_center #+begin_details Implementation #+begin_src emacs-lisp -r -n (o-defblock parallel (cols 2) (bar nil) "Place ideas side-by-side, possibly with a separator. There are COLS many columns, and they may be seperated by black solid vertical rules if BAR is a non-nil value. Writing “#+begin_parallel 𝓃 :bar (any text except ‘nil’)” will produce a parallel of 𝓃 many columns, possibly seperated by solid rules, or a “bar”. The contents of the block may contain ‘#+columnbreak:’ to request a columnbreak. This has no effect on HTML export since HTML describes how text should be formatted on a browser, which can dynamically shrink and grow and thus it makes no sense to have hard columnbreaks. We do replace such declarations by ‘


’, which sometimes accomplishes the desired goal. " (let ((rule (pcase backend (`latex (if bar 2 0)) (_ (if bar "solid" "none")))) (contents′ (s-replace "#+columnbreak:" (if (equal 'latex backend) "\\columnbreak" "@@html:


@@") contents))) (format (pcase backend (`latex "\\par \\setlength{\\columnseprule}{%s pt} \\begin{minipage}[t]{\\linewidth} \\begin{multicols}{%s} %s \\end{multicols}\\end{minipage}") (_ "

%s
")) rule cols contents′))) #+end_src # [[color:orange][Going forward,]] Going forward, it would be desirable to have the columns take a specified percentage of the available width ---whereas currently it splits it uniformly. Such a feature would be useful in cases where one column is wide and the others are not. # 2pt ↦ 0.5pt ?? Maybe use a #+bind? or make column seperator a top-level configurable item? Let's have some sanity tests... #+begin_src emacs-lisp :tangle tests.el :comments link (deftest "Parallel blocks work as expected" [parallel block] (⇝ (⟰ "#+begin_parallel 2 :bar yes-or-any-other-text X ,#+columnbreak: Y Z ,#+end_parallel") ;; The result is 2 columns with a solid rule between them ;; and it contains the user's text along with the “#+columnbreak”. "
" (* anything) "X" (* anything) "


" ;; “#+columnbreak” above (* anything) "Y" (* anything) "Z" (* anything))) #+end_src #+end_details #+html:
#+begin_box Example _This_ #+begin_src org :tangle no ,#+begin_parallel 2 :bar yes-or-any-other-text X ,#+columnbreak: Y Z ,#+end_parallel #+end_src _Yields_ #+begin_parallel 2 :bar t X #+columnbreak: Y Z #+end_parallel #+end_box #+begin_center ( The [[https://www.gnu.org/software/emacs/manual/html_mono/eww.html][Emacs Web Wowser]], ~M-x eww~, does not display =parallel= environments as desired. ) #+end_center ** COMMENT Older setup :Possibly_delete: :PROPERTIES: :CUSTOM_ID: COMMENT-Older-setup :END: :Header: #+BEGIN_SRC emacs-lisp ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Parallel blocks: 𝓃parallel[NB] for n:2..5, optionally with ‘N’o ‘b’ar ;; in-between the columns. ;; ;; Common case is to have three columns, and we want to avoid invoking the ;; attribute via org, so making this. #+END_SRC :End: We want to be able to reduce the amount of whitespace noise in our articles, and so use the =parallel= block to place ideas side-by-side ---with up to the chosen limit of 5 columns. #+caption: Displaying thoughts side-by-side ^_^ Top is browser, then Emacs, then PDF [[file:images/parallel.png]] #+LATEX_HEADER: \usepackage{multicol} | =#+LATEX_HEADER: \usepackage{multicol}= | I initially used the names =parallel𝓃= but names ending with a number =𝓃= did not inherit highlighting, so I shifted the number to being a prefix instead. + For LaTeX, new lines are used to suggest opportunities for column breaks and are needed even if explicit columnbreaks are declared. + Use the nullary switch =:columnbreak:= to request a columnbreak; this has no effect on HTML export since HTML describes how text should be formatted on a browser, which can dynamically shrink and grow and thus it makes no sense to have hard columnbreaks. + We also provide ~𝓃parallelNB~ for users who want ‘N’o ‘B’ar separator between columns. #+BEGIN_SRC emacs-lisp (cl-loop for cols in '("1" "2" "3" "4" "5") do (cl-loop for rule in '("solid" "none") do (eval (read (concat "(defun o--" cols "parallel" (if (equal rule "solid") "" "NB") "(backend contents)" "(format (pcase backend" "(`html \"

\")" "(`latex \"\\\\par \\\\setlength{\\\\columnseprule}{" (if (equal rule "solid") "2" "0") "pt}" " \\\\begin{minipage}[t]{\\\\linewidth}" " \\\\begin{multicols}{" cols "}" " %s" " \\\\end{multicols}\\\\end{minipage}\"))" "(s-replace \":columnbreak:\" (if (equal 'html backend) \"\" \"\\\\columnbreak\") contents)))"))))) #+END_SRC # # Musa: If I use “
%s
”, the ‘>’ causes some undesirable whitespace # in the first column. Hence, omitting it. We also use ~parallel~ as an alias for ~2parallel~: The forward declaration is tangled at the top of the file, whereas the alias declarations are declared upon mode activation ---after such functions have been declared. #+BEGIN_SRC emacs-lisp :noweb-ref forward-decls :tangle no (declare-function o--2parallel "org-special-block-extras" t t) (declare-function o--2parallelNB "org-special-block-extras" t t) #+END_SRC #+BEGIN_SRC emacs-lisp :noweb-ref enable-mode :tangle no (defalias 'o--parallel #'o--2parallel) (defalias 'o--parallelNB #'o--2parallelNB) #+END_SRC * Colours :PROPERTIES: :CUSTOM_ID: Colours :END: Let's develop blocks for colouring text and link types for inline colouring; e.g., [[doc:o--color][color]] and [[doc:o--teal][teal]]. + E.g., ~[[brown: Hello, World!]]~ ⇒ [[brown: Hello, World!]] + E.g., ~brown:Friends!~ ⇒ brown:Friends! ( Note the ‘!’ is not coloured; use ~[[...]]~ ! ) #+begin_box :background-color custard Use kbd:M-x_list-colors-display to see a list of defined colour names in Emacs ---see [[http://muug.ca/mirror/ctan/macros/latex/contrib/xcolor/xcolor.pdf][xcolor]] for the LaTeX side and [[https://htmlcolorcodes.com/color-names/][htmlcolorcodes.com]] for the HTML side, or just visit http://latexcolor.com/ for both. # Use =M-: (defined-colors)= to see all colours that are supported on your Emacs. #+end_box #+html:
:Examples: #+BEGIN_SRC emacs-lisp :results value :wrap no :tangle no (s-join "\n\n" (cl-loop for c in org-special-block-extras/colors collect (format "#+begin_%s\n This text is %s!\n#+end_%s" c c c))) #+END_SRC :End: #+begin_details A Picture and Block Examples [[file:images/colours.jpg]] -------------------------------------------------------------------------------- #+begin_parallel #+begin_black This text is black! #+end_black #+begin_blue This text is blue! #+end_blue #+begin_brown This text is brown! #+end_brown # #+begin_cyan # This text is cyan! # #+end_cyan #+begin_darkgray This text is darkgray! #+end_darkgray #+begin_gray This text is gray! #+end_gray #+begin_green This text is green! #+end_green #+begin_lightgray This text is lightgray! #+end_lightgray #+begin_lime This text is lime! #+end_lime #+begin_magenta This text is magenta! #+end_magenta #+begin_olive This text is olive! #+end_olive #+begin_orange This text is orange! #+end_orange #+begin_pink This text is pink! #+end_pink #+begin_purple This text is purple! #+end_purple #+begin_red This text is red! #+end_red #+begin_teal This text is teal! #+end_teal #+begin_violet This text is violet! #+end_violet #+begin_white This text is white! #+end_white #+begin_yellow This text is yellow! #+end_yellow #+end_parallel #+end_details :Header: #+BEGIN_SRC emacs-lisp ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Load support for 20 colour custom blocks and 20 colour link types #+END_SRC :End: #+html:
#+begin_details Implementation of numerous colour blocks/links We declare a list of colors that should be available on most systems. Then using this list, we evaluate the code necessary to produce the necessary functions that format special blocks. # - To add support for a colour =𝒞=, simply # ~(push '𝒞 o--colors)~. # # By default, Org uses the ~graphicx~ LaTeX package which let's us colour text ---see its documentation [[http://ctan.mirror.rafal.ca/macros/latex/required/graphics/grfguide.pdf][here]]. For example, in an ~#+begin_export latex~ block, the following produces blue coloured text. #+begin_example latex { \color{blue} This is a sample text in blue. } #+end_example # Below, we format colour block types to essentially format block contents like # this. #+BEGIN_SRC emacs-lisp (defvar o--colors '(black blue brown cyan darkgray gray green lightgray lime magenta olive orange pink purple red teal violet white yellow) "Colours that should be available on all systems.") (cl-loop for colour in o--colors do (eval `(o-defblock ,colour (the-color "black" :face `(:foreground ,(format "%s" (quote ,colour)))) nil ,(format "Show text in %s color." colour) (let () (format (pcase backend (`latex "\\begingroup\\color{%s}%s\\endgroup\\,") (_ "%s")) (quote ,colour) contents))))) #+END_SRC #+RESULTS: Let's have some sanity tests... #+begin_src emacs-lisp :tangle tests.el :comments link (deftest "It is an HTML span styled red that contains the user's text" [color red block] (⇝ (⟰ "#+begin_red My cool thoughts... ,#+end_red") "" (* anything) "My cool thoughts" (* anything) "")) ;; We have an HTML span styled with the user's color and it contains the user's text (deftest "It works as expected" [color pink block] (⇝ (⟰ "#+begin_color pink My cool thoughts... ,#+end_color") "" (* anything) "My cool thoughts" (* anything) "")) #+end_src :Old: #+BEGIN_SRC emacs-lisp :tangle no (defvar o--colors '(black blue brown cyan darkgray gray green lightgray lime magenta olive orange pink purple red teal violet white yellow) "Colours that should be available on all systems.") (cl-loop for colour in o--colors do (eval (read (format "(defun o--%s (backend contents) (format (pcase backend (`latex \"\\\\begingroup\\\\color{%s}%%s\\\\endgroup\\\\,\") (_ \"%%s\")) contents))" colour colour colour)))) #+END_SRC :End: #+end_details #+html:
#+begin_details Implementation of ‘color’ For faster experimentation between colours, we provide a generic =color= block that consumes a color argument. #+begin_src emacs-lisp (o-defblock color (color black :face (lambda (colour) `(:foreground ,(format "%s" colour)))) nil "Format text according to a given COLOR, which is black by default." (format (pcase backend (`latex "\\begingroup\\color{%s}%s\\endgroup\\,") (`html "%s")) color contents)) #+end_src [Posterity] Old version which did /not/ allow ~wombo~ colour: #+begin_src emacs-lisp :tangle no (o-defblock color (color black :face (lambda (colour) (if (member (intern colour) o--colors) `(:foreground ,(format "%s" colour)) `(:height 300 :underline (:color "red" :style wave) :overline "red" :strike-through "red")))) nil "Format text according to a given COLOR, which is black by default." (format (pcase backend (`latex "\\begingroup\\color{%s}%s\\endgroup\\,") (`html "%s")) color contents)) #+end_src #+RESULTS: :Old: #+begin_src emacs-lisp :tangle no (defun o--color (backend contents) "Format CONTENTS according to the ‘:color:’ they specify for BACKEND." (-let* (((contents′ . (&alist 'color)) (o--extract-arguments contents 'color)) (block-coloring (intern (format "o--%s" (s-trim color))))) (if (member (intern (s-trim color)) o--colors) (funcall block-coloring backend contents′) (error "Error: “#+begin_color:%s” ⇒ Unsupported colour!" color)))) #+end_src :End: #+end_details Moreover, we have that the syntax =red:text= /renders/ ‘text’ with the colour red in *both* the Emacs interface and in exported backends. [[file:images/colour_links.png]] :Old: #+begin_src emacs-lisp :tangle no ;; [[𝒞:text₀][text₁]] ⇒ Colour ‘textₖ’ by 𝒞, where k is 1, if present, otherwise 0. ;; If text₁ is present, it is suggested to use ‘color:𝒞’, defined below. (cl-loop for colour in o--colors do (org-link-set-parameters (format "%s" colour) :follow `(lambda (path) (message "Colouring “%s” %s." path (quote ,colour))) :export `(lambda (label description backend) (-let [block-colouring (intern (format "o--%s" (quote ,colour)))] (funcall block-colouring backend (or description label)))) :face `(:foreground ,(format "%s" colour)))) ;; Generic ‘color’ link type [[color:𝒞][text]] ⇒ Colour ‘text’ by 𝒞. ;; If 𝒞 is an unsupported colour, ‘text’ is rendered in large font ;; and surrounded by red lines. (org-link-set-parameters "color" :follow (lambda (_)) :face (lambda (colour) (if (member (intern colour) o--colors) `(:foreground ,(format "%s" colour)) `(:height 300 :underline (:color "red" :style wave) :overline "red" :strike-through "red"))) :help-echo (lambda (_ __ position) (save-excursion (goto-char position) (-let* (((&plist :path) (cadr (org-element-context)))) (if (member (intern path) o--colors) "Colour links just colour the descriptive text" (format "Error: “color:%s” ⇒ Unsupported colour!" path))))) :export (lambda (colour description backend) (-let [block-colouring (intern (format "o--%s" colour))] (if (member (intern colour) o--colors) (funcall block-colouring backend description) (error "Error: “color:%s” ⇒ Unsupported colour!" colour))))) #+end_src :End: #+begin_center Observe: red:this green:is cyan:super teal:neato, purple:amigos! and [[color:brown][this is brown ‘color’ link]] and [[color:orange][this one is an orange ‘color’ link!]] +Also: If we try to use an unsupported colour ‘wombo’, we render the+ +descriptive text larger in Emacs along with a tooltip explaining why this is the case; e.g., =[[color:wombo][hi]]=.+ ~[[color:#fad][Using Hex colour code!]]~ ⇒ [[color:#fad][Using Hex colour code!]] ;-) #+end_center ( Markdown does not support colour; go look at the HTML or PDF! ) **   ~latex-definitions~ for hiding LaTeX declarations in HTML :PROPERTIES: :CUSTOM_ID: latex-definitions-for-hiding-LaTeX-declarations-in-HTML :END: :Fails_idea: Larger example: #+begin_mathjax red:\Sigma #+end_mathjax :End: Before indicating desirable next steps, let us produce an incidentally useful special block type. #+latex: \vspace{1em} We may use LaTeX-style commands such as ~{\color{red} x}~ by enclosing them in =$=-symbols to obtain ${\color{red}x}$ and other commands to present mathematical formulae in HTML. This is known as the MathJax tool ---Emacs' default HTML export includes it. #+latex: \vspace{1em} It is common to declare LaTeX definitions for convenience, but such declarations occur within ~$~-delimiters and thereby produce undesirable extra whitespace. We declare the ~latex_definitions~ block type which avoids displaying such extra whitespace in the resulting HTML. #+begin_details ‘latex-definitions’ Implementation #+begin_src emacs-lisp (o-defblock latex-definitions nil nil "Declare but do not display the CONTENTS according to the BACKEND." (format (pcase backend ('html "

\\[%s\\]

") (_ "%s")) raw-contents)) #+end_src #+RESULTS: :OLD: #+begin_src emacs-lisp :tangle no (defun o--latex-definitions (backend contents) "Declare but do not display the CONTENTS according to the BACKEND." (cl-loop for (this that) in (-partition 2 '("

" "" "

" "" "\\{" "{" "\\}" "}")) do (setq contents (s-replace this that contents))) (format (pcase backend ('html "

\\[%s\\]

") (_ "%s")) contents)) #+end_src - Org escapes ~{,}~ in LaTeX export, so we need to ‘unescape’ them. This is clearly a hack. :End: #+end_details #+latex: \iffalse Here ---which you cannot see, as /desired/---is an example usage, where we declare ~\LL~ to produce a violet left parenthesis. We then use these to produce an example of quantification notation. #+begin_latex-definitions \def\LL{\color{violet}(} \def\RR{\color{violet})} #+end_latex-definitions # +begin_org-demo :source-color white :result-color white $$ {\color{teal}\bigoplus} _{ {\color{violet} x} = {\color{red} a}} ^{\color{cyan} b} {\color{brown}{\,f\, x}} \quad=\quad {\color{brown}{f\,\LL {\color{red} a} \RR}} \;{\color{teal}\oplus}\; {\color{brown}{f \, \LL a + 1 \RR }} \;{\color{teal}\oplus}\; {\color{brown}{f \, \LL a + 2 \RR }} \;{\color{teal}\oplus}\; \cdots \;{\color{teal}\oplus}\; {\color{brown}{f \, \LL {\color{cyan} b} \RR}} $$ | [[teal:⊕]] | /Loop sequentially with loop-bodies fused using [[teal:⊕][⊕]]/ | | /[[violet:x]]/ | /Use [[violet:x][x]] as the name of the current element/ | | /[[red:a]]/ | /Start with [[violet:x][x]] being [[red:a][a]]/ | | /[[cyan:b]]/ | /End with [[violet:x][x]] being [[cyan:b][b]]/ | | /[[color:brown][f x]]/ | /At each [[violet:x][x]] value, compute [[color:brown][f x]]/ | # Note that /[[color:brown][f x]]/ is obtained by =/[[color:brown][f x]]/.= # +end_org-demo ( Markdown does not support MathJax; go look at the HTML or PDF! ) #+latex: \fi -------------------------------------------------------------------------------- Unfortunately, MathJax does not easily support arbitrary HTML elements to occur within the =$=-delimiters ---see [[https://stackoverflow.com/questions/58883048/mathjax-or-similar-render-arbitrary-html-element-inside-expression][this]] and [[https://github.com/mathjax/MathJax/issues/1707][this]] for ‘workarounds’. As such, the MathJax producing the above example is rather ugly whereas its subsequent explanatory table is prettier on the writer's side. :Verbatim_pasted_from_the_above_THIS_link: MathJax will not process math that contains HTML tags (other than a select few), so you will not be able to do the kind of replacements inside an expression like you are attempting to do here. #+begin_export html
$$x + \left(\,\insertHTML{}\,\right) + y$$
$$x+\left(\insertHTML{}\right)+y$$ #+end_export :End: #+latex: \vspace{1em} [[color:orange][Going forward,]] it would be nice to easily have our colour links work within a mathematical special block. #+latex: \vspace{1em} [[color:orange][Moreover,]] it would be nice to extend the =color= block type to take multiple arguments, say, =c₁ c₂ … cₙ= such that: | /n/ | Behaviour | |---+------------------------------------------------------------------------------------| | 0 | No colouring; likewise if no arguments altogether | | 1 | Colour all entries using the given colour c₁ | | /n/ | Paragraph --region separated by a new line-- =i= is coloured by =cₖ= where =k = i mod n= | Besides having a colourful article, another usage I envision for this generalisation would be when rendering text in multiple languages; e.g., use red and blue to interleave Arabic poetry with its English translation. * Nice Keystroke Renditions: kbd:C-h_h :PROPERTIES: :CUSTOM_ID: kbd:nice-keystroke-renditions :END: Anyone who writes /about/ Emacs will likely want to mention keystrokes in an aesthetically pleasing way, such as [[kbd:C-u 80 -]] to insert 80 dashes, or kbd:C-c_C-e_h_o to export an Org-mode file to HTML, or the useful . - ~kbd:𝒳~ will show a tooltip defining 𝒳, as an Emacs Lisp function, if possible. For example, ~kbd:C-h_h~ is kbd:C-h_h; and likewise == is (we need to use ~<...>~ since punctuation is not picked up as part of link labels). In contrast, ~kbd:nope~ renders as kbd:nope /without/ a tooltip (nor a red border). You can also supply explicit tooltip description: =[[kbd:key sequence][description]]= will show as [[kbd:key sequence][description]]; i.e., the key sequence in nice key font along with a tooltip explaining it. # To make a screenshot, add the following to the above :PROPERTIES: drawer. # :UNNUMBERED: t # #+attr_html: :width 80% :height 80%s [[file:./images/kbd.png]] #+begin_details Implementation #+begin_src emacs-lisp (o-deflink kbd "Show keysequence O-LABEL in a nice grey button-like font, along with a tooltip of its documentation, if any. Such links do not get folded in [[bracket]] style, and are rendered as buttons within Emacs. Moreover, O-LABEL may use ‘_’ in-lieu of spaces or [[bracket]] link notation. Examples: [[kbd:C-x C-s]] ≈ ≈ kbd:C-x_C-s" [:display 'full :let (the-label (s-trim (s-replace "_" " " o-label)) lisp-func (ignore-errors (cl-second (help--analyze-key (kbd the-label) the-label))) tooltip (or o-description (ignore-errors (documentation lisp-func)) "") tooltip? (not (equal tooltip "")) style (if tooltip? "border-color: red" "") keystrokes (format "%s" style the-label)) ;; o-description is always nil when it comes to deciding the :face. :face (list :inherit 'custom-button :box (if tooltip? "red" t)) :help-echo (format "%s ∷ %s\n%s" the-label (or lisp-func "") tooltip)] (if (equal o-backend 'latex) (format "\\texttt{%s}" the-label) (if tooltip? ;; The style=⋯ is to remove the underlying caused by . (format "%s\">%s" the-label (or lisp-func "") (o-html-export-preserving-whitespace tooltip) keystrokes) keystrokes))) #+end_src The following styling rule is used to make the keystrokes displayed nicely. #+begin_src emacs-lisp :noweb-ref enable-mode :tangle no (defvar o--kbd-html-setup nil "Has the necessary keyboard styling HTML beeen added?") (unless o--kbd-html-setup (setq o--kbd-html-setup t) (setq org-html-head-extra (concat org-html-head-extra " "))) #+end_src Let's have some sanity tests... #+begin_src emacs-lisp :tangle tests.el :comments link (deftest "It becomes tags, but final symbol non-ascii *may* be ignored" [kbd direct-org-links] (⇝ (⟰ "kbd:C-u_80_-∀") "

\nC-u 80_-∀

")) (deftest "[[It]] becomes tags" [kbd square-org-links] (⇝ (⟰ "[[kbd:C-u_80_-]]") "

\nC-u 80 -

")) (deftest " becomes tags, and surrounding space is trimmed" [kbd angle-org-links] (⇝ (⟰ "") "

\nC-u 80 -

")) (deftest "It has a tooltip documenting the underlying Lisp function, when possible" [kbd tooltip] (⇝ (⟰ "") "Uses the next face from ‘hi-lock-face-defaults’ without prompting,
unless you use a prefix argument.
Uses ‘find-tag-default-as-symbol-regexp’ to retrieve the symbol at point.

This uses Font lock mode if it is enabled; otherwise it uses overlays,
in which case the highlighting will not update as you type. The Font
Lock mode is considered ''enabled'' in a buffer if its ‘major-mode’
causes ‘font-lock-specified-p’ to return non-nil, which means
the major mode specifies support for Font Lock." (* anything) "M-s h .
")) #+end_src #+end_details :Hide: #+HTML: #+HTML: :End: *   /“Link Here!”/ & OctoIcons :PROPERTIES: :CUSTOM_ID: Link-Here-OctoIcons :END: Use the syntax =link-here:name= to create an anchor link that alters the URL with =#name= as in “link-here:name” ---it looks and behaves like the Github generated links for a heading. Use case: Sometimes you want to explicitly point to a particular location in an article, such as within a =#+begin_details= block, this is a possible way to do so. Likewise, get OctoIcons with the syntax =octoicon:𝒳= where =𝒳= is one of =home, link, mail, report, tag, clock=: octoicon:home, octoicon:link, octoicon:mail, octoicon:report, octoicon:tag, octoicon:clock. - Within =octoicon:𝒳= and =link-here:𝒳= the label =𝒳= determines the OctoIcon shown and the name of the local link to be created, respectively. + Descriptions, as in =[[link:label][description]]=, are ignored. - Besides the HTML backend, such links are silently omitted. #+begin_details Six OctoIcons and Implementation The following SVGs are obtained from: https://primer.style/octicons/ #+begin_src emacs-lisp (defvar o--supported-octoicons (-partition 2 '( home "" link "" mail "" report "" tag "" clock "")) "An association list of supported OctoIcons. Usage: (cadr (assoc 'ICON o--supported-octoicons))") #+end_src #+begin_src emacs-lisp (o-deflink octoicon "Show an OctoIcon: home, link, mail, report, tag, clock" [:help-echo "Show an OctoIcon: home, link, mail, report, tag, clock"] (unless (member (intern o-label) '(home link mail report tag clock)) (error "octoicon:%s ⇒ This label is not supported!" o-label)) (if (not (equal o-backend 'html)) "" (s-collapse-whitespace (cadr (assoc (intern o-label) o--supported-octoicons))))) (o-deflink link-here "Export a link to the current location in an Org file." [:help-echo (format "This is a local anchor link named “%s”" path)] (if (not (equal o-backend 'html)) "" (format (s-collapse-whitespace "%s") o-label o-label (cadr (assoc 'link o--supported-octoicons))))) #+end_src # [[color:orange][Going forward,]] Going forward, it would be desirable to provide a non-whitespace alternative for the LaTeX rendition. More usefully, before the HTML export hook, we could place such ‘link-here’ links before every org-title produce clickable org-headings, similar to Github's ---the necessary ingredients are likely [[https://github.com/alhassy/emacs.d#ensuring-useful-html-anchors][here]]. # [[color:orange][Moreover]], Moreover, it may be useful to have =octoicon:𝒳|url= so the resulting OctoIcon is optionally a link to the given =url=. Another direction would be to support more, if not all, the possible OctoIcons. #+end_details #+html:
#+begin_org-demo Example :source-color custard :result-color custard link-here:example-location (Click the icon and see the URL has changed!) #+end_org-demo #+begin_details Tests #+begin_src emacs-lisp :tangle tests.el :comments link (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!)") " (Click the icon and see the URL has changed!)" (* anything))) #+end_src #+end_details * Badge Links :PROPERTIES: :CUSTOM_ID: Badge-Links :END: Badges provide a quick and colourful summary of key features of a project, such as whether it's maintained, its license, and if it's documented. # Badges are little coloured boxes; e.g., those found all over Github. Such # eye-candy can be obtained from https://shields.io/, which has many examples. #+caption: An Emacs interface to https://shields.io/ [[file:images/badges.png]] #+begin_quote As people who are passionate about writing great code we display "badges" in our code repositories to signal to fellow developers that we set ourselves high standards for the code we write, think of them as the software-equivalent of the brand on your jeans or other reliable product. --- [[https://github.com/dwyl/repo-badges][repo-badges]] #+end_quote :Header: #+begin_src emacs-lisp ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; The badge link types ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; #+end_src #+RESULTS: :End: # :style sequential #+begin_org-demo What are badges? badge:Let_me_google_that|for_you!|orange|https://lmgtfy.app/?q=badge+shields.io&iie=1|Elixir or [[badge: Let me google that | for you! |orange| https://lmgtfy.app/?q=badge+shields.io&iie=1 |Elixir]] or badge:Let_me_*not*_google_that|for_you #+end_org-demo Notice that the orange:orange badge points to a URL and so is clickable, whereas the green:green one does not point to a URL and so is not clickable. #+begin_details Automated Tests Let's have some sanity tests... #+begin_src emacs-lisp :tangle tests.el :comments link (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") "" "")) (deftest "It works when we use [[link]] syntax with generous spaces and newlines" [badge] (⇝ (⟰ "[[badge: Let me google that | for you! | orange | https://lmgtfy.app/?q=badge+shields.io&iie=1|Elixir]]") "" (* anything) "")) (deftest "It works when only the first 2 arguments are provided; asterisks are passed unaltered into the first argument" [badge] (⇝ (⟰ "badge:Let_me_*not*_google_that|for_you") "")) (deftest "It works when all 5 arguments are provided - URL ‘here’ makes it a local link" [badge] (⇝ (⟰ "badge:key|value|informational|here|Elixir") "" "")) (deftest "We can use spaces, commas, dashes, and percentage symbols in the first argument" [badge] (⇝ (⟰ "badge:example_with_spaces,_-,_and_%|points_right_here|orange|here") "" "")) (deftest "It works when only first 2 arguments are given: Default colour & logo are green & no logo shown" [badge] (⇝ (⟰ "badge:key|value") "")) (deftest "When only a key is provided, the value slot is shown as an empty green stub" [badge] (⇝ (⟰ "badge:key") "")) (deftest "When only a value is provided, only the value is shown in a default green ---no stub for the missing key, yay" [badge] (⇝ (⟰ "badge:|value") "")) (deftest "It's only a green stub when provided with an empty key and empty value" [badge] (⇝ (⟰ "badge:||green") "")) (deftest "It's only a green stub when we use a totally [[badge:]]" [badge] (⇝ (⟰ "[[badge:]]") "")) #+end_src #+end_details ** Begin omitting from LaTeX output :ignore: :PROPERTIES: :CUSTOM_ID: Begin-omitting-from-LaTeX-output :END: #+latex: \iffalse ** Example Badges :PROPERTIES: :CUSTOM_ID: Example-Badges :END: The general syntax is as follows, with *only the first 2* are mandatory, with the colour defaulting to green, and the url and logo both to nil. We can thus have ~badge:label|message~ :Hide: #+begin_example org # Standard template badge:key|value|colour|url|logo # Minimal template badge:key|value # Only show a coloured logo pointed to its location badge:||colour|here|logo #+end_example :End: + badge:key|value|informational|here|Elixir ⇐ ~badge:key|value|informational|here|Elixir~ - Standard template; with URL pointing to current location which is named =#key= ---click on it and look at your browser's URL ;-) + badge:example_with_spaces,_-,_and_%|points_right_here|orange|here ⇐ ~badge:example_with_spaces,_-,_and_%|points_right_here|orange|here~ # - Use ‘_’ to denote spaces + badge:no_colour|given ⇐ =badge:key|value=. + badge:empty_value||informational ⇐ =badge:empty_value||informational= + badge:|value ⇐ =badge:|value= + badge:||green ⇐ =badge:||green= ( No key; nor value! ) Also: [[badge:]] ⇐ ~[[badge:]]~. #+begin_box General Syntax #+begin_example org badge:your_key|its_neato_value|some_url|a_logo_as_shown_below [[badge: your key | its neato value | some url | a logo as shown below]] reddit-subscribe-to:exact-name-of-a-subreddit github-stars:user-name/repository-name github-watchers:user-name/repository-name github-forks:user-name/repository-name github-followers:user-name twitter-follow:user-name tweet:url #+end_example For now, only ~badge:~ badges redirect to their URL, if any, upon a user's click. Within Emacs, hovering over a badge displays a tooltip indicating which values corresponds to which badge fields. You can make your /own/ badge types with doc:o-make-badge. #+end_box #+begin_details o-make-badge The implementation is a bit lengthy since it attempts to capture a useful portion of the shilelds.io badge interface. #+begin_src emacs-lisp (cl-defmacro o-make-badge (name &optional social-shields-name social-url social-shields-url ) "Make a link NAME whose export is presented as an SVG badge. If the link is intend to be a social badge, then, adhering to shield.io conventions, the appropriate SOCIAL-SHIELDS-NAME must be given. The SOCIAL-URL is a URL that the badge points to and it should have a single ‘%s’ where the link label argument will go. Some social badges have a uniform URL, but otherwise, such as twitter, deviate and so need their own SOCIAL-SHIELDS-URL, which has a single ‘%s’ for the link's label argument. ---------------------------------------------------------------------- E.g., to create a badge named “go”: (o-make-badge \"go\") Then the following exports nicely from an Org file: go:key|value|blue|here|gnu-emacs" `(o-deflink ,(intern name) "Export a Shields.io badge, with general Syntax: badge:key|value|colour|url|logo. Precise details for each argument are shown in the Emacs tooltip for this badge." [:display 'full :follow (--> (s-split "|" path) (or (nth 3 it) o-label) (browse-url it)) :help-echo (-let [ (key value color url logo) (mapcar #'s-trim (s-split "|" o-label)) ] (lf-string "${o-label} \nGeneral Syntax: badge:key|value|colour|url|logo Key : ${key} Value : ${(or value \"\")} Colour : ${(or color \"green\")} URL : ${(or url \"\")} Logo : ${(or logo \"\")} This results in an SVG badge “[key∣value]”, where the ‘key’ is coloured grey and the ‘value’ is coloured ‘color’; for the HTML backend, and otherwise are silently omitted. Descriptions are ignored; i.e., ‘[[badge:label][description]]’ is the same as ‘[[badge:label]]’. ► ‘key’ and ‘value’ have their underscores interpreted as spaces. ⇒ ‘__’ is interpreted as an underscore; Of course, you can write ‘’, then ‘⋯’ may have multiword, spaced, content. ⇒ ‘|’ is not a valid substring, but ‘-, %, ?’ are okay. ► ‘|color|url|logo’ are optional; ⇒ If ‘url’ is not present, the resulting badge is not a hyperlink. ⇒ if ‘url’ is ‘here’ then we have a local link; i.e., the resulting badge behaves like ‘link-here:key’. ⇒ ‘color’ may be: ‘brightgreen’ or ‘success’, ‘red’ or ‘important’, ‘orange’ or ‘critical’, ‘lightgrey’ or ‘inactive’, ‘blue’ or ‘informational’, or ‘green’, ‘yellowgreen’, ‘yellow’, ‘blueviolet’, ‘ff69b4’, etc. Consult https://htmlcolorcodes.com/ to see the HEX codes of other colours. ⇒ ‘logo’ examples and how they look can be found at https://alhassy.github.io/org-special-block-extras/#Example-Badge-Icons See also: https://alhassy.github.io/org-special-block-extras/#Common-Project-Badges"))] ;; :export #'o--link--badge (if (equal o-backend 'latex) "" (-let [ (key value color url logo) (mapcar #'s-trim (s-split "|" o-label)) ] (format (pcase ,(if social-shields-name `(format ,social-url o-label) 'url) ("here" (format "%%s" (s-replace "%" "%%" key) (s-replace "%" "%%" key))) ("" "%s") ;; e.g., badge:key|value|color||logo ('nil "%s") ;; e.g., badge:key|value|color (t (format "%%s" (s-replace "%" "%%" ,(if social-shields-name `(format ,social-url o-label) 'url))))) ,(if social-shields-name (if social-shields-url `(format ,social-shields-url o-label) `(format "" ,social-shields-name o-label)) '(format "" (url-hexify-string (s-replace "-" "--" key)) (url-hexify-string (s-replace "-" "--" (or value ""))) color logo))))))) #+end_src # (o-make-badge "badge") # # badge:hola # (Tooltip wont show for Org comments; uncomment previous line to see tooltip!) Notice that we can have special ~%~-HTML characters in URLs; e.g., ~~ ⇒ . #+end_details :OLD: #+begin_src emacs-lisp :tangle no (defun o--link--badge (label _ backend &optional social) "Export a link presented as an SVG badge. The LABEL should be of the shape ‘key|value|color|url|logo’ resulting in a badge “|key|value|” where the ‘key’ is coloured grey and the ‘value’ is coloured ‘color’. The optional SOCIAL toggle indicates if we want an icon for Twitter, Reddit, Github, etc, instead of a badge. When SOCIAL is provided, we interpret LABEL as an atomic string. + Only the syntax ‘badge:key|value|color|url’ is supported. - ‘key’ and ‘value’ have their underscores interpreted as spaces. ⇒ Underscores are interpreted as spaces; ⇒ ‘__’ is interpreted as an underscore; ⇒ ‘|’ is not a valid substring, but ‘-, %, ?’ are okay. - ‘|color|url|logo’ are optional; if ‘url’ is ‘|here’ then the resulting badge behaves like ‘link-here:key’. - ‘color’ may be: ‘brightgreen’ or ‘success’, ‘red’ or ‘important’, ‘orange’ or ‘critical’, ‘lightgrey’ or ‘inactive’, ‘blue’ or ‘informational’, or ‘green’, ‘yellowgreen’, ‘yellow’, ‘blueviolet’, ‘ff69b4’, etc. + Such links are displayed using a SVG badges and so do not support the DESCRIPTION syntax ‘[[link:label][description]]’. + Besides the HTML BACKEND, such links are silently omitted." (-let* (((lbl msg clr url logo) (s-split "|" label)) (_ (unless (or (and lbl msg) social) (error "%s\t⇒\tBadges are at least “badge:key|value”!" label))) ;; Support dashes and other symbols (_ (unless social (setq lbl (s-replace "-" "--" lbl) msg (s-replace "-" "--" msg)) (setq lbl (url-hexify-string lbl) msg (url-hexify-string msg)))) (img (format "" lbl msg clr (if logo (concat "?logo=" logo) "")))) (when social (--> `(("reddit" "https://www.reddit.com/r/%s") ("github/followers" "https://www.github.com/%s?tab=followers") ("github/forks" "https://www.github.com/%s/fork") ("github" "https://www.github.com/%s") ("twitter/follow" "https://twitter.com/intent/follow?screen_name=%s") ("twitter/url" ,(format "https://twitter.com/intent/tweet?text=%s:&url=%%s" (s-replace "%" "%%" (url-hexify-string o--link--twitter-excitement))) ,(format "" label))) (--filter (s-starts-with? (cl-first it) social) it) (car it) (or it (error "Badge: Unsupported social type “%s”" social)) (setq url (format (cl-second it) label) img (or (cl-third it) (format "" social label))))) (pcase backend ('html (if url (if (equal url "here") (format "%s" lbl lbl img) (format "%s" url img)) img)) ('latex "") ;; Markdown syntax: [![image title](url to get image)](url to go to on click) (_ (setq img (s-chop-suffix "\">" (s-chop-prefix "" ) #+end_src -------------------------------------------------------------------------------- A few more in one, convoluted, go. #+begin_src emacs-lisp ;; MA: I don't think this is ideal for long-term maintainability, see ‘:OLD’ below. (cl-loop for (social url name) in '(("reddit/subreddit-subscribers" "https://www.reddit.com/r/%s" "reddit") ("github" "https://www.github.com/%s") ("github/stars" "https://www.github.com/%s/stars") ("github/watchers" "https://www.github.com/%s/watchers") ("github/followers" "https://www.github.com/%s?tab=followers") ("github/forks" "https://www.github.com/%s/fork") ("twitter/follow" "https://twitter.com/intent/follow?screen_name=%s")) for name′ = (or name (s-replace "/" "-" social)) do (eval `(o-make-badge ,name′ ,social ,url))) #+end_src :OLD: #+begin_src emacs-lisp :tangle no (o-make-badge "twitter-follow" "twitter/follow" "https://twitter.com/intent/follow?screen_name=%s") (o-make-badge "reddit" "reddit/subreddit-subscribers" "https://www.reddit.com/r/%s") (o-make-badge "github" "github" "https://www.github.com/%s") (o-make-badge "github-followers" "github/followers" "https://www.github.com/%s?tab=followers") (o-make-badge "github-stars" "github/stars" "https://www.github.com/%s/stars") (o-make-badge "github-forks" "github/forks" "https://www.github.com/%s/fork") (o-make-badge "github-watchers" "github/watchers" "https://www.github.com/%s/watchers") #+end_src :END: #+end_details Here are some examples. + Things I like: reddit:emacs reddit:common_lisp reddit:coolguides reddit:shia # reddit-subscribe-to:LispMemes # reddit-subscribe-to:ProgrammerHumor + Info about my cheatsheets: github-stars:alhassy/CheatSheet github-watchers:alhassy/CheatSheet github-forks:alhassy/CheatSheet tweet:https://github.com/alhassy/org-special-block-extras + My profile: github-followers:alhassy twitter-follow:musa314 ** Example Colours :PROPERTIES: :CUSTOM_ID: Example-Colours :END: #+begin_org-demo + badge:|red|red badge:|critical|critical + badge:|blue|blue badge:|informational|informational + badge:|brightgreen|brightgreen badge:|success|success + badge:|orange|orange badge:|important|important + badge:|lightgrey|lightgrey badge:|inactive|inactive + badge:|green|green + badge:|yellowgreen|yellowgreen + badge:|yellow|yellow + badge:|blueviolet|blueviolet + badge:|ff69b4|ff69b4 + badge:|9cf|9cf + ... Consult https://htmlcolorcodes.com/ to see the HEX code of any other colour you wish to use; e.g., badge:|1d8348|1d8348 #+end_org-demo ** Example Badge Icons :PROPERTIES: :CUSTOM_ID: Example-Badge-Icons :END: Here are a few free SVG icons for popular brands from https://simpleicons.org/. To use any of the names below, just write, say, ~badge:||grey||SVG-NAME-HERE~. #+macro: showicon badge:||grey||$1 $1 #+begin_parallel 4 :bar t + “Fire” :: - {{{showicon(Elixir)}}} - {{{showicon(tinder)}}} - {{{showicon(codeigniter)}}} - {{{showicon(prometheus)}}} - {{{showicon(sparkpost)}}} -------------------------------------------------------------------------------- + “Messaging” :: - {{{showicon(quip)}}} - {{{showicon(WeChat)}}} - {{{showicon(google-hangouts)}}} - {{{showicon(hackhands)}}} - {{{showicon(google-messages)}}} - {{{showicon(Slack)}}} # Tor badge:||grey|here|Tor -------------------------------------------------------------------------------- + “Emacs” :: - {{{showicon(gnu-emacs)}}} - {{{showicon(spacemacs)}}} - {{{showicon(vim)}}} - {{{showicon(neovim)}}} - {{{showicon(gnu)}}} - {{{showicon(github)}}} - {{{showicon(acm)}}} - {{{showicon(wikipedia)}}} - {{{showicon(microsoft-excel)}}} - {{{showicon(microsoft-word)}}} - {{{showicon(dropbox)}}} - {{{showicon(google-scholar)}}} - {{{showicon(google)}}} - {{{showicon(google-translate)}}} - {{{showicon(ghost)}}} - {{{showicon(helm)}}} - {{{showicon(apache-openoffice)}}} - {{{showicon(buffer)}}} - {{{showicon(adobe-fonts)}}} - {{{showicon(google-calendar)}}} -------------------------------------------------------------------------------- + “Social” :: - {{{showicon(google-cast)}}} - {{{showicon(youtube)}}} - {{{showicon(discord)}}} - {{{showicon(facebook)}}} - {{{showicon(google-hangouts)}}} - {{{showicon(whatsapp)}}} - {{{showicon(skype)}}} # - {{{showicon(arxive)}}} - {{{showicon(reddit)}}} - {{{showicon(stack-overflow)}}} - {{{showicon(stack-exchange)}}} - {{{showicon(linkedin)}}} - {{{showicon(twitter)}}} - {{{showicon(jabber)}}} -------------------------------------------------------------------------------- + “Lightbulb” :: - {{{showicon(lighthouse)}}} - {{{showicon(google-keep)}}} - {{{showicon(minds)}}} -------------------------------------------------------------------------------- + “Programming” :: - {{{showicon(git)}}} - {{{showicon(ruby)}}} - {{{showicon(scala)}}} - {{{showicon(OCaml)}}} - {{{showicon(javascript)}}} - {{{showicon(gnu-bash)}}} - {{{showicon(powershell)}}} - {{{showicon(LaTeX)}}} - {{{showicon(java)}}} - {{{showicon(kotlin)}}} - {{{showicon(haskell)}}} - {{{showicon(coffeescript)}}} - {{{showicon(purescript)}}} - {{{showicon(rust)}}} - {{{showicon(typescript)}}} - {{{showicon(css3)}}} - {{{showicon(python)}}} - {{{showicon(c)}}} - {{{showicon(clojure)}}} - {{{showicon(lua)}}} - {{{showicon(adobe-acrobat-reader)}}} - {{{showicon(perl)}}} # c-+-+ badge:||grey|here|c-+-+ # c# badge:||grey|here|c# -------------------------------------------------------------------------------- + “Miscellaneous” :: - {{{showicon(read-the-docs)}}} - {{{showicon(buy-me-a-coffee)}}} - {{{showicon(gimp)}}} - {{{showicon(mega)}}} - {{{showicon(nintendo-3ds)}}} - {{{showicon(paypal)}}} - {{{showicon(pinboard)}}} - {{{showicon(mocha)}}} - {{{showicon(Gitea)}}} - {{{showicon(instacart)}}} - {{{showicon(openStreetMap)}}} - {{{showicon(amazon)}}} - {{{showicon(svg)}}} - {{{showicon(rss)}}} - {{{showicon(swagger)}}} - {{{showicon(pastebin)}}} - {{{showicon(skyliner)}}} - {{{showicon(iTunes)}}} - {{{showicon(gulp)}}} - {{{showicon(leaflet)}}} - {{{showicon(youtube-gaming)}}} - {{{showicon(GIMP)}}} - {{{showicon(atom)}}} # pokemon badge:||grey|here|pokemon # badge:||grey|here|1001-track-lists # badge:||grey|here|auda-city # badge:||grey|here|dribble #+end_parallel ** Common Project Badges ---with copyable source :PROPERTIES: :CUSTOM_ID: Common-Project-Badges :END: #+macro: withsrc $1 @@html:
src@@ ~$1~ @@html:
@@ # +begin_org-demo :style sequential + {{{withsrc(badge:Emacs|23/26/28|green|https://www.gnu.org/software/emacs|gnu-emacs)}}} + {{{withsrc(badge:Org|9.3.6|blue|https://orgmode.org|gnu)}}} + {{{withsrc([[badge:org-special-block-extras|1.0|informational|https://alhassy.github.io/org-special-block-extras/README.html|Gnu-Emacs][org-special-block-extras badge]])}}} # + twitter:https://github.com/alhassy/org-special-block-extras + {{{withsrc([[badge:melpa|pending|critical|https://github.com/alhassy/emacs.d#use-package-the-start-of-initel|github][melpa badge]])}}} + {{{withsrc([[badge:docs|literate|success|https://github.com/alhassy/emacs.d#what-does-literate-programming-look-like|read-the-docs][read-the-docs badge]])}}} {{{withsrc(badge:wiki|github|informational|here|wikipedia)}}} + {{{withsrc(badge:code_coverage|88%|green|here|codecov)}}} {{{withsrc(badge:build|passing|success|here|azure-pipelines)}}} + {{{withsrc(badge:author|musa_al-hassy|purple|https://alhassy.github.io/|nintendo-3ds)}}} + {{{withsrc(badge:author|musa_al-hassy|purple|https://alhassy.github.io/|gimp)}}} + {{{withsrc([[badge:license|GNU_3|informational|https://www.gnu.org/licenses/gpl-3.0.en.html|read-the-docs][gnu 3 license badge]])}}} + {{{withsrc(badge:issue_tracking|github|informational|here|github)}}} + {{{withsrc(badge:help_forum|discourse|informational|here|discourse)}}} + {{{withsrc(badge:social_chat|gitter|informational|https://gitter.im/explore|gitter)}}} + {{{withsrc(badge:Maintained?|yes|success)}}} {{{withsrc(badge:Maintained?|no|critical)}}} {{{withsrc(badge:No_Maintenance_Intended|×|critical|http://unmaintained.tech/)}}} + {{{withsrc(badge:website|up|success)}}} {{{withsrc(badge:website|down|critical)}}} + {{{withsrc(badge:Ask_me|anything|1abc9c)}}} {{{withsrc(badge:contributions|welcome|green|https://github.com/alhassy/org-special-block-extras/issues)}}} + # Org macro args are delimited by ‘,’! # Wont show correctly: {{{withsrc(badge:Made_with|Python,_LaTeX,_MathJax,_and_Emacs_Org-mode|1f425)}}} badge:Made_with|Python,_LaTeX,_MathJax,_and_Emacs_Org-mode|1f425 @@html:
src@@ ~badge:Made_with|Python,_LaTeX,_MathJax,_and_Emacs_Org-mode|1f425~ @@html:
@@ # +end_org-demo ** End omitting from LaTeX output :ignore: :PROPERTIES: :CUSTOM_ID: End-omitting-from-LaTeX-output :END: #+latex: \fi ** Next Steps :PROPERTIES: :CUSTOM_ID: Next-Steps-badges :END: [[color:orange][Going forward,]] it would be desirable to provide non-whitespace alternatives for the LaTeX backend. {{{newline}}} [[remark:Author][That is why no examples are shown in the PDF]] It may be useful to colour the =|=-separated fields of a badge link. # This would make the interface more welcoming to new users. * Tooltips for Glossaries, Dictionaries, and Documentation :PROPERTIES: :CUSTOM_ID: Tooltips-for-Glossaries-Dictionaries-and-Documentation :END: ** Intro, motivating examples :ignore: :PROPERTIES: :CUSTOM_ID: COMMENT-Intro :END: #+begin_documentation Category Theory :label "cat" A theory of typed composition; e.g., typed monoids. #+end_documentation Let's make a link type =doc= that shows a tooltip documentation ---e.g., glossary or abbreviation--- for a given label. E.g., user-declared doc:cat and Emacs-retrieved doc:loop and doc:thread-last ^_^ # LaTeX does not allow figures in minipages, so just ignoring them in Latex # export #+latex: \iffalse #+begin_details Eye-candy ---A gallery of images #+caption: Tooltips for documentation and glossary items --in the browser! [[file:images/tooltips_browser.png]] #+caption: Tooltips for documentation and glossary items --in Emacs! [[file:images/tooltips_emacs.png]] #+caption: Tooltips for documentation and glossary items --in the PDF! [[file:images/tooltips_pdf.png]] :Hide: #+caption: Declaring documentation-glossary items [[file:images/tooltips_declaration.png]] :End: #+end_details #+latex: \fi #+begin_box Full Example :background-color custard #+latex_header: \newunicodechar{π}{\ensuremath{\pi}} _User enters …_ #+begin_src org ,#+begin_documentation Existential Angst :label "ex-angst" A negative feeling arising from freedom and responsibility. Also known as 1. /Existential Dread/, and 2. *Existential Anxiety*. Perhaps a distraction, such as [[https://www.w3schools.com][visiting W3Schools]], may help ;-) Then again, ~coding~ can be frustrating at times, maybe have a slice of pie with maths by reading “$e^{i×π} + 1 = 0$” as a poem ;-) ,#+end_documentation #+end_src #+begin_documentation Existential Angst :label ex-angst A negative feeling arising from freedom and responsibility. Also known as 1. /Existential Dread/, and 2. *Existential Anxiety*. Perhaps a distraction, such as [[https://www.w3schools.com][visiting W3Schools]], may help ;-) Then again, ~coding~ can be frustrating at times, maybe have a slice of pie with maths by reading “$e^{i×π} + 1 = 0$” as a poem ;-) #+end_documentation _Then…_ =doc:ex-angst= gives us doc:ex-angst, or using a description: [[doc:ex-angst][“existence is pain”?]] ( ~[[doc:ex-angst][“existence is pain”?]]~ ) :Labelless_example: #+begin_documentation Existential Angsty A negative feeling arising from freedom and responsibility. bye #+end_documentation doc:Existential_Angsty :End: #+end_box #+begin_quote As it stands, Emacs tooltips *only* appear after an export has happened: The export updates the dictionary variable which is used for the tooltips utility. The ~:label~ is optional; when not provided it defaults to the name of the entry with spaces replaced by ‘_’. For more, see doc:o--documentation. Entry names do not need to be unique, but labels do! ( The labels are like the addresses used to look up a person's home. ) # Moreover, a documentation block may have multiple entries ---the =:name:= argument # must appear first, then the =:label:=, and the remaining text is the # description-documentation of the given name. #+end_quote #+begin_details ‼TODO More Examples #+begin_documentation Natural Transformation :label "natural-transformation" Methods that do not perform any “type casing”. #+end_documentation | Supported | Example | Source | |-------------------------------------------+-----------------------+------------------------------------------------| | No description | doc:cat | ~doc:cat~ | | With description and code font | [[doc:natural-transformation][=polymorphism=]] | ~[[doc:natural-transformation][=polymorphism=]]~ | | Fallback: Arbitrary ELisp Docs (function) | doc:thread-first | ~doc:thread-first~ | | Fallback: Arbitrary ELisp Docs (variable) | doc:user-mail-address | =doc:user-mail-address= | | Fallback: Arbitrary English word | doc:user | =doc:user= | Notice how hovering over items makes them appear, but to make them disappear you should click on them or scroll away. This is ideal when one wants to have multiple ‘definitions’ visible ;-) Below are the entries in my personal glossary ;-) #+begin_src emacs-lisp :exports results :results replace value :results table pp replace :tangle no (ignore-errors (--map (concat "doc:" it) (mapcar #'car o--docs-from-libraries))) #+end_src # or ... #+begin_src emacs-lisp :wrap box :exports results :tangle no (o-docs-load-libraries) (ignore-errors (s-join "\n" (reverse (--map (concat "doc:" it) (mapcar #'car o--docs-from-libraries))))) #+end_src ‼ TODO :Ignore: #+RESULTS: #+begin_box doc:cat doc:category_theory doc:Category_Theory doc:cat doc:existential_angst doc:Existential_Angst doc:ex-angst doc:existential_angsty doc:Existential_Angsty doc:nil doc:nil doc:hussain doc:Hussain doc:Karbala doc:Cosmic_Tragedy doc:hello doc:Hello doc:nil doc:graph doc:Graph doc:nil doc:expression doc:Expression doc:nil doc:induction doc:Induction doc:nil doc:textual_substitution doc:Textual_Substitution doc:nil doc:inference_rule doc:Inference_Rule doc:nil doc:logic doc:Logic doc:nil doc:theorem doc:Theorem doc:nil doc:metatheorem doc:Metatheorem doc:nil doc:calculus doc:Calculus doc:Propositional_Calculus doc:semantics doc:Semantics doc:Axiomatic_Semantics doc:Operational_Semantics doc:calculational_proof doc:Calculational_Proof doc:nil doc:programming doc:Programming doc:nil doc:specification doc:Specification doc:nil doc:proving_is_programming doc:Proving_is_Programming doc:nil doc:algorithmic_problem_solving doc:Algorithmic_Problem_Solving doc:nil doc:natural_transformation doc:Natural_Transformation doc:nat-trans doc:polymorphism doc:category_theory doc:Category_Theory doc:associative doc:Associative doc:nil doc:identity doc:Identity doc:nil doc:distributive doc:Distributive doc:nil doc:commutative doc:Commutative doc:nil doc:reflexive doc:Reflexive doc:nil doc:transitive doc:Transitive doc:nil doc:symmetric doc:Symmetric doc:nil doc:antisymmetric doc:Antisymmetric doc:nil doc:asymmetric doc:Asymmetric doc:nil doc:preorder doc:Preorder doc:nil doc:equivalence doc:Equivalence doc:nil doc:linear doc:Linear doc:nil doc:semilinear doc:Semilinear doc:nil doc:univalent doc:Univalent doc:nil doc:total doc:Total doc:nil doc:map doc:Map doc:nil doc:surjective doc:Surjective doc:nil doc:injective doc:Injective doc:nil doc:bijective doc:Bijective doc:nil doc:iso doc:Iso doc:nil doc:difunctional doc:Difunctional doc:nil doc:nil doc:hussain doc:Hussain doc:Karbala doc:Cosmic_Tragedy doc:hello doc:Hello doc:nil doc:graph doc:Graph doc:nil doc:expression doc:Expression doc:nil doc:induction doc:Induction doc:nil doc:textual_substitution doc:Textual_Substitution doc:nil doc:inference_rule doc:Inference_Rule doc:nil doc:logic doc:Logic doc:nil doc:theorem doc:Theorem doc:nil doc:metatheorem doc:Metatheorem doc:nil doc:calculus doc:Calculus doc:Propositional_Calculus doc:semantics doc:Semantics doc:Axiomatic_Semantics doc:Operational_Semantics doc:calculational_proof doc:Calculational_Proof doc:nil doc:programming doc:Programming doc:nil doc:specification doc:Specification doc:nil doc:proving_is_programming doc:Proving_is_Programming doc:nil doc:algorithmic_problem_solving doc:Algorithmic_Problem_Solving doc:nil doc:natural_transformation doc:Natural_Transformation doc:nat-trans doc:polymorphism doc:category_theory doc:Category_Theory doc:cat doc:associative doc:Associative doc:nil doc:identity doc:Identity doc:nil doc:distributive doc:Distributive doc:nil doc:commutative doc:Commutative doc:nil doc:reflexive doc:Reflexive doc:nil doc:transitive doc:Transitive doc:nil doc:symmetric doc:Symmetric doc:nil doc:antisymmetric doc:Antisymmetric doc:nil doc:asymmetric doc:Asymmetric doc:nil doc:preorder doc:Preorder doc:nil doc:equivalence doc:Equivalence doc:nil doc:linear doc:Linear doc:nil doc:semilinear doc:Semilinear doc:nil doc:univalent doc:Univalent doc:nil doc:total doc:Total doc:nil doc:map doc:Map doc:nil doc:surjective doc:Surjective doc:nil doc:injective doc:Injective doc:nil doc:bijective doc:Bijective doc:nil doc:iso doc:Iso doc:nil doc:difunctional doc:Difunctional doc:nil doc:nil doc:hussain doc:Hussain doc:Karbala doc:Cosmic_Tragedy doc:hello doc:Hello doc:nil doc:graph doc:Graph doc:nil doc:expression doc:Expression doc:nil doc:induction doc:Induction doc:nil doc:textual_substitution doc:Textual_Substitution doc:nil doc:inference_rule doc:Inference_Rule doc:nil doc:logic doc:Logic doc:nil doc:theorem doc:Theorem doc:nil doc:metatheorem doc:Metatheorem doc:nil doc:calculus doc:Calculus doc:Propositional_Calculus doc:semantics doc:Semantics doc:Axiomatic_Semantics doc:Operational_Semantics doc:calculational_proof doc:Calculational_Proof doc:nil doc:programming doc:Programming doc:nil doc:specification doc:Specification doc:nil doc:proving_is_programming doc:Proving_is_Programming doc:nil doc:algorithmic_problem_solving doc:Algorithmic_Problem_Solving doc:nil doc:natural_transformation doc:Natural_Transformation doc:nat-trans doc:polymorphism doc:category_theory doc:Category_Theory doc:cat doc:associative doc:Associative doc:nil doc:identity doc:Identity doc:nil doc:distributive doc:Distributive doc:nil doc:commutative doc:Commutative doc:nil doc:reflexive doc:Reflexive doc:nil doc:transitive doc:Transitive doc:nil doc:symmetric doc:Symmetric doc:nil doc:antisymmetric doc:Antisymmetric doc:nil doc:asymmetric doc:Asymmetric doc:nil doc:preorder doc:Preorder doc:nil doc:equivalence doc:Equivalence doc:nil doc:linear doc:Linear doc:nil doc:semilinear doc:Semilinear doc:nil doc:univalent doc:Univalent doc:nil doc:total doc:Total doc:nil doc:map doc:Map doc:nil doc:surjective doc:Surjective doc:nil doc:injective doc:Injective doc:nil doc:bijective doc:Bijective doc:nil doc:iso doc:Iso doc:nil doc:difunctional doc:Difunctional doc:nil doc:category_theory doc:Category_Theory doc:cat doc:existential_angst doc:Existential_Angst doc:ex-angst doc:existential_angsty doc:Existential_Angsty doc:nil doc:nil doc:hussain doc:Hussain doc:Karbala doc:Cosmic_Tragedy doc:hello doc:Hello doc:nil doc:graph doc:Graph doc:nil doc:expression doc:Expression doc:nil doc:induction doc:Induction doc:nil doc:textual_substitution doc:Textual_Substitution doc:nil doc:inference_rule doc:Inference_Rule doc:nil doc:logic doc:Logic doc:nil doc:theorem doc:Theorem doc:nil doc:metatheorem doc:Metatheorem doc:nil doc:calculus doc:Calculus doc:Propositional_Calculus doc:semantics doc:Semantics doc:Axiomatic_Semantics doc:Operational_Semantics doc:calculational_proof doc:Calculational_Proof doc:nil doc:programming doc:Programming doc:nil doc:specification doc:Specification doc:nil doc:proving_is_programming doc:Proving_is_Programming doc:nil doc:algorithmic_problem_solving doc:Algorithmic_Problem_Solving doc:nil doc:natural_transformation doc:Natural_Transformation doc:nat-trans doc:polymorphism doc:category_theory doc:Category_Theory doc:cat doc:associative doc:Associative doc:nil doc:identity doc:Identity doc:nil doc:distributive doc:Distributive doc:nil doc:commutative doc:Commutative doc:nil doc:reflexive doc:Reflexive doc:nil doc:transitive doc:Transitive doc:nil doc:symmetric doc:Symmetric doc:nil doc:antisymmetric doc:Antisymmetric doc:nil doc:asymmetric doc:Asymmetric doc:nil doc:preorder doc:Preorder doc:nil doc:equivalence doc:Equivalence doc:nil doc:linear doc:Linear doc:nil doc:semilinear doc:Semilinear doc:nil doc:univalent doc:Univalent doc:nil doc:total doc:Total doc:nil doc:map doc:Map doc:nil doc:surjective doc:Surjective doc:nil doc:injective doc:Injective doc:nil doc:bijective doc:Bijective doc:nil doc:iso doc:Iso doc:nil doc:difunctional doc:Difunctional doc:hussain doc:Hussain doc:Karbala doc:Cosmic_Tragedy doc:hello doc:Hello doc:nil doc:graph doc:Graph doc:nil doc:expression doc:Expression doc:nil doc:induction doc:Induction doc:nil doc:textual_substitution doc:Textual_Substitution doc:nil doc:inference_rule doc:Inference_Rule doc:nil doc:logic doc:Logic doc:nil doc:theorem doc:Theorem doc:nil doc:metatheorem doc:Metatheorem doc:nil doc:calculus doc:Calculus doc:Propositional_Calculus doc:semantics doc:Semantics doc:Axiomatic_Semantics doc:Operational_Semantics doc:calculational_proof doc:Calculational_Proof doc:nil doc:programming doc:Programming doc:nil doc:specification doc:Specification doc:nil doc:proving_is_programming doc:Proving_is_Programming doc:nil doc:algorithmic_problem_solving doc:Algorithmic_Problem_Solving doc:nil doc:natural_transformation doc:Natural_Transformation doc:nat-trans doc:polymorphism doc:category_theory doc:Category_Theory doc:cat doc:associative doc:Associative doc:nil doc:identity doc:Identity doc:nil doc:distributive doc:Distributive doc:nil doc:commutative doc:Commutative doc:nil doc:reflexive doc:Reflexive doc:nil doc:transitive doc:Transitive doc:nil doc:symmetric doc:Symmetric doc:nil doc:antisymmetric doc:Antisymmetric doc:nil doc:asymmetric doc:Asymmetric doc:nil doc:preorder doc:Preorder doc:nil doc:equivalence doc:Equivalence doc:nil doc:linear doc:Linear doc:nil doc:semilinear doc:Semilinear doc:nil doc:univalent doc:Univalent doc:nil doc:total doc:Total doc:nil doc:map doc:Map doc:nil doc:surjective doc:Surjective doc:nil doc:injective doc:Injective doc:nil doc:bijective doc:Bijective doc:nil doc:iso doc:Iso doc:nil doc:difunctional doc:Difunctional doc:nil #+end_box :end: In the export, it looks like some names are repeated, but in the source one would notice that we have /labels/ in both upper and lower cases and with underscores ;-) They just happen to be referring to the same /named/ entry. ------- My personal documentation library can be seen here: badge:Htmlized|Org|green|https://alhassy.github.io/org-special-block-extras/documentation.org.html or badge:Pretty|HTML|green|https://alhassy.github.io/org-special-block-extras/documentation.html. #+end_details #+begin_details Tests #+begin_src emacs-lisp :tangle tests.el :comments link (deftest "It gives a tooltip whose title is the Lisp docs of APPLY" [doc] (⇝ (⟰ "doc:apply") "" (documentation #'apply))) "\">apply")) (setq angst (⟰ "#+begin_documentation Existential Angst :label \"ex-angst\" A negative feeling arising from freedom and responsibility. Also known as 1. /Existential Dread/, and 2. *Existential Anxiety*. Perhaps a distraction, such as [[https://www.w3schools.com][visiting W3Schools]], may help ;-) Then again, ~coding~ can be frustrating at times, maybe have a slice of pie with maths by reading “$e^{i×π} + 1 = 0$” as a poem ;-) ,#+end_documentation We now use this as doc:ex-angst.")) (deftest "Documentation blocks are not exported; they produce a new osbe--docs entry" [doc documentation o--name&doc] (should (o-docs-get "ex-angst"))) ;; Upon export, the #+begin_documentation is /not/ present. ;; We have the text outside that block only. ;; Along, with a htmlified tooltip of the new entry. (deftest "The osbe--docs entry of the documentation block appears within a tooltip" [doc documentation o--name&doc] (⇝ angst "

We now use this as Existential Angst.

")) #+end_src #+end_details ** Implementation Details: =doc= link, ~documentation~ block, and [[https://iamceege.github.io/tooltipster/#triggers][tooltipster]] :PROPERTIES: :CUSTOM_ID: The-doc-link-type :END: We begin by making use of a list of documentation-glossary entries ---a lightweight database of information, if you will. #+begin_box Overview of relevant Lisp + doc:o--docs is the global variable that holds the glossary/database for the current session. Entries may be inserted with doc:o-docs-set and retrieved with doc:o-docs-get. + doc:o-docs-fallback is a /fallback method/ for retriving documentation-glossary entries from elsewhere besides the current buffer; e.g., using Emacs' doc:documentation function or the ~wn~ WordNet command for definitions of English words. We use doc:o-docs-load-libraries to load documentation-glossary entries from external files. + doc:o-docs-insert for interactively inserting links for documented-glossary words. #+end_box #+begin_src emacs-lisp (defvar o--docs nil "An alist of (LABEL NAME DESCRIPTION) entries; our glossary. Example setter: 0. (o-docs-set \"os\" \"Emacs\" \"A place wherein I do all of my computing.\") Example getters: 0. (o-docs-get LABEL) 1. (-let [(name description) (cdr (assoc LABEL o--docs))] ⋯) See also `o--docs-from-libraries' and `o-docs-load-libraries'.") (cl-defun o-docs-set (label name description) "Add a new documentation-glossary entry, if it is not already present. We associate LABEL to have title NAME and glossary value DESCRIPTION. Example usage: (o-docs-set \"cat\" \"Category Theory\" \"A theory of typed composition; e.g., typed monoids.\")" (add-to-list 'o--docs (list label name description))) (cl-defun o-docs-get (label) "Return the name and documentation-glossary values associated with LABEL. It returns a list of length 2. Example uses: ;; Get the Lisp documentation of `thread-last' (o-docs-get \"thread-last\") ;; Get the English definition of ‘computing’ (o-docs-get \"computing\") We look for LABEL from within the current buffer first, using `o--docs', and otherwise look among the loaded libraries, using `o--docs-from-libraries', and, finally, look for the documentation entry using `o-docs-fallback'." (cdr (or (assoc label o--docs) (assoc label o--docs-from-libraries) (funcall o-docs-fallback label) (error "Error: No documentation-glossary entry for “%s”!" label)))) (cl-defun o-docs-insert () "Insert a “doc:𝒳” link from user's documentation-glossary database. It can be tricky to remember what you have, or what documentation entries mention, and so this command gives a searchable way to insert doc links." (interactive) (thread-last (cl-remove-duplicates (-concat o--docs o--docs-from-libraries) :test (lambda (x y) (cl-equalp (car x) (car y)))) (--map (format "%s ∷ %s" (car it) (cl-third it))) (completing-read "Insert doc link ∷ ") (s-split "∷") car (concat "doc:") (insert))) #+end_src :Hide_startup_code: #+name: startup-code #+begin_src emacs-lisp :tangle no (o-docs-set "cat" "Category Theory" "A theory of typed composition; e.g., typed monoids.") #+end_src :End: We may wish to use Emacs' doc:documentation (or doc:lf-documentation) command to retrieve entries ---this is useful for an online article that refers to unfamiliar Emacs terms ;-) To avoid copy-pasting documentation entries from one location to another, users may declare a fallback method. Besides Emacs' doc:documentation, the fallback can be refer to a user's personal ‘global glossary’ variable ---which may live in their Emacs' init file---, for more see: doc:o-docs-load-libraries #+begin_src emacs-lisp (defvar o-docs-fallback (lambda (label) (list ;; label label ;; name label ;; documentation (or (ignore-errors (documentation (intern label))) (ignore-errors (documentation-property (intern label) 'variable-documentation)) (-let [it (shell-command-to-string (format "wn %s -over -synsn" label))] (if (s-blank-p it) (error "Error: No documentation-glossary entry for “%s”!" label) it))))) "The fallback method to retriving documentation or glossary entries. We try to retrive the Emacs Lisp function documentation of the given LABEL, if possible, otherwise we try to retrive the Emacs Lisp variable documentation, and if that fails then we look up the word in the English dictionary. The English definition is obtained from the command line tool ‘wn’, WordNet.") #+end_src #+begin_details Implementation matter for user libraries #+begin_src emacs-lisp (defvar o-docs-libraries nil "List of Org files that have ‘#+begin_documentation’ blocks that should be loaded for use with the ‘doc:𝒳’ link type.") #+end_src In your init, you could do the following. ( See the installation instructions below, for more! ) #+name: startup-code #+begin_src emacs-lisp :tangle no (setq o-docs-libraries '("~/org-special-block-extras/documentation.org")) #+end_src My personal documentation library can be seen here: badge:Htmlized|Org|green|https://alhassy.github.io/org-special-block-extras/documentation.org.html or badge:Pretty|HTML|green|https://alhassy.github.io/org-special-block-extras/documentation.html. #+begin_src emacs-lisp (cl-defun o-docs-load-libraries (&optional (libs o-docs-libraries)) "Load documentation-glossary libraries LIBS. If no LIBS are provided, simply use those declared o-docs-libraries. See `o-docs-from-libraries'." (interactive) (cl-loop for lib in libs do (with-temp-buffer (insert-file-contents lib) ;; doc only activates after an export (-let [org-export-with-broken-links t] (org-html-export-as-html)) (kill-buffer) (delete-window) (setq o--docs-from-libraries (-concat o--docs o--docs-from-libraries)) (setq o--docs nil)))) (defvar o--docs-from-libraries nil "The alist of (label name description) entries loaded from the libraries. The ‘doc:𝒳’ link will load the libraries, possibly setting this variable to ‘nil’, then make use of this variable when looking for documentation strings. Interactively call `o-docs-load-libraries' to force your documentation libraries to be reloaded. See also `o-docs-libraries'.") #+end_src When the mode is actually enabled, then we load the user's libraries. #+begin_src emacs-lisp :noweb-ref enable-mode :tangle no ;; Ensure user's documentation libraries have loaded (unless o--docs-from-libraries (o-docs-load-libraries)) #+end_src #+RESULTS: #+end_details Let's keep track of where documentation comes from ---either the current article or from the fallback--- so that we may process it later on. #+begin_src emacs-lisp (defvar o--docs-actually-used nil "Which words are actually cited in the current article. We use this listing to actually print a glossary using ‘show:GLOSSARY’.") #+end_src #+RESULTS: : o--docs-actually-used Now HTML exporting such links as tooltips and displaying them in Emacs as tooltips happens in two stages: First we check the documentation, if there is no entry, we try the fallback ---if that falls, an error is reported at export time. E.g., upon export =doc:wombo= will produce a no-entry error. # doc:thread-first #+begin_details ‘doc’ link implementation #+name: startup-code #+begin_src emacs-lisp (o-deflink doc "Export O-LABEL as itself, or as the provided O-DESCRIPTION, along with a tooltip that shows the user's documentation-glossary for o-LABEL and using that entry's name when no O-DESCRIPTION is provided." [:let (entry (o-docs-get o-label) name (cl-first entry) docs (cl-second entry) display-name (or o-description name)) :help-echo (format "[%s] %s :: %s" o-label name docs) :face '(custom-button)] (add-to-list 'o--docs-actually-used (list o-label name docs)) (pcase o-backend (`html (format "%s" (o-html-export-preserving-whitespace docs) display-name)) ;; Make the current word refer to its glosary entry; ;; also declare the location that the glossary should refer back to. (`latex (format (concat "\\hyperref" "[o-glossary-%s]{%s}" "\\label{o-glossary" "-declaration-site-%s}") label display-name label)))) ;; WHERE ... (defun o-html-export-preserving-whitespace (s) "Make Org-markup'd string S ready for HTML presentation, preserving whitespace. This is orthogonal to the `org-export-string-as' command; i.e., (org-export-string-as s 'html :body-only-please) does something else! In particular, what this yields is an HTML rendition that does not account for whitespace, such as indentation and newlines. " ;; Make it look pretty! (thread-last s (s-replace-regexp "\\#\\+begin_src [^\n]*\n" "
")
    (s-replace-regexp "\\( \\)*\\#\\+end_src\n" "
") (s-replace-regexp "\\#\\+begin_export [^\n]*\n" "") (s-replace-regexp "\\( \\)*\\#\\+end_export" "") (s-replace " " " ") ; Preserve newlines (s-replace "\n" "
") ; Preserve whitespace (s-replace-regexp "\\#\\+begin_example
" "") (s-replace-regexp "\\#\\+end_example
" "") ; (s-replace-regexp "\\#\\+begin_src \\(.\\)*\\#\\+end_src)" "
\1
") ; (s-replace-regexp "\\#\\+begin_src [^<]*
" "
")
    ; (s-replace-regexp "
\\( \\)*\\#\\+end_src
" "
\1
") ;; Translate Org markup ;; Only replace /.*/ by .* when it does not have an alphanum,:,/,< before it. (s-replace-regexp "\\([^a-z0-9A-Z:/<]\\)/\\(.+?\\)/" "\\1\\2") (s-replace-regexp "\\*\\(.+?\\)\\*" "\\1") (s-replace-regexp "\\~\\([^ ].*?\\)\\~" "\\1") ;; No, sometimes we want equalities. ;; (s-replace-regexp "=\\([^ \n].*?\\)=" "\\1") (s-replace-regexp "\\$\\(.+?\\)\\$" "\\1") (s-replace-regexp "\\[\\[\\(.*\\)\\]\\[\\(.*\\)\\]\\]" "\\2 (\\1)") ;; 5+ dashes result in a horizontal line (s-replace-regexp "-----+" "
") ;; Spacing in math mode (s-replace-regexp "\\\\quad" " ") (s-replace-regexp "\\\\," " ") ;; en space (s-replace-regexp "\\\\;" " ") ;; em space ;; The presence of ‘\"’ in tooltips breaks things, so omit them. (s-replace-regexp "\\\"" "''"))) #+end_src Since tooltips are not clickable and do not support MathJax, we have manually transpiled what are perhaps the most common instances of Org-markup: Bold, emphasises, code/verbatim, math, and links. #+end_details # Our mode highlights documented text, with tooltips, as red. # #+begin_src emacs-lisp :noweb-ref enable-mode # (setq org-html-head-extra # (concat org-html-head-extra # (format ""))) # #+end_src Things look great at the HTML side and on the Emacs side for *consuming* documented text. Besides being inconvenient, we cannot with good conscious force the average user to write Lisp as we did for the ~doc:cat~ entry. We turn to the problem of *producing* documentation entries with a block type interface... #+begin_details ‘documentation’ Implementation: An Example of Mandatory Arguments and Using ‘raw-contents’ #+BEGIN_SRC emacs-lisp (o-defblock documentation (name (error "Documentation block: Name must be provided")) (label nil show nil color "green") "Register the dictionary entries in CONTENTS to the dictionary variable. The dictionary variable is ‘o--docs’. A documentation entry may have its LABEL, its primary identifier, be: 1. Omitted 2. Given as a single symbol 3. Given as a many aliases '(lbl₀ lbl₁ … lblₙ) The third case is for when there is no canonical label to refer to an entry, or it is convenient to use multiple labels for the same entry. In all of the above cases, two additional labels are included: The entry name with spaces replaced by underscores, and again but all lower case. Documentation blocks are not shown upon export; unless SHOW is non-nil, in which case they are shown using the ‘box’ block, with the provided COLOR passed to it. In the futture, it may be nice to have an option to render tooltips. That'd require the ‘doc:𝒳’ link construction be refactored via a ‘defun’." (unless (consp label) (setq label (list label))) (push (s-replace " " "_" name) label) (push (downcase (s-replace " " "_" name)) label) (cl-loop for l in label do (add-to-list 'o--docs (mapcar #'s-trim (list (format "%s" l) name (substring-no-properties raw-contents))))) ;; Should the special block show something upon export? (if show (o--blockcall box name :background-color color raw-contents) "")) #+END_SRC #+RESULTS: | :export | (lambda (label description backend) (s-replace-all `((#+end_export . ) (,(format #+begin_export %s backend) . )) (o--documentation backend description label))) | :help-echo | (lambda (window object position) (save-excursion (goto-char position) (-let* (((&plist :path :format :raw-link :contents-begin :contents-end) (cadr (org-element-context))) (description (when (equal format 'bracket) (copy-region-as-kill contents-begin contents-end) (substring-no-properties (car kill-ring))))) (format %s | :Older_approach: #+BEGIN_SRC emacs-lisp :tangle no (defun o--documentation (_ contents) "Register the dictionary entries in CONTENTS to the dictionary variable. The dictionary variable is ‘o--docs’. Documentation blocks are not shown upon export." ;; Strip out any

tags ;; Musa: Make these three lines part of the core utility? (setq contents (substring-no-properties contents)) (setq contents (s-replace-regexp "

" "" contents)) (setq contents (s-replace-regexp "

" "" contents)) (setq contents (s-trim contents)) (cl-loop for entry in (cdr (s-split ":name:" contents)) do (-let [(contents′ . (&alist 'label 'name)) (o--extract-arguments (s-concat ":name:" entry) 'label 'name)] (unless (and label name) (error (message-box (concat "#+begin_documentation: " "Ensure the entry has a :name followed by a :label " "\n\n " contents)))) (add-to-list 'o--docs (mapcar #'s-trim (list label name contents′))))) ;; The special block is not shown upon export. "") #+END_SRC :End: #+end_details # #  [[https://iamceege.github.io/tooltipster/#triggers][Tooltipster]] ---Fast, Sleek, & Beautiful Tooltips Thus far, Org entities are converted into HTML tags such as == for italicised text. However, HTML's default tooltip utility ---using ~title="⋯"~ in a ~div~--- does not render arbitrary HTML elements. Moreover, the default tooltip utility is rather slow. As such, we move to using /tooltipster/. The incantation below sets up the required magic to make this happen. #+begin_details "Fast, Sleek, and Beautiful Tooltips: Touching ‘org-html-head-extra’" #+begin_src emacs-lisp :noweb-ref enable-mode :tangle no (defvar o--tooltip-html-setup nil "Has the necessary HTML beeen added?") (unless o--tooltip-html-setup (setq o--tooltip-html-setup t) (setq org-html-head-extra (concat org-html-head-extra " "))) #+end_src Note that we have a conditional in the middle to avoid loading jQuery multiple times ---e.g., when one uses the =read-the-org= them in combination with this library. Multiple imports lead to broken features. #+end_details ** Wait, what about the LaTeX? :PROPERTIES: :CUSTOM_ID: hola :END: A PDF is essentially a fancy piece of paper, so tooltips will take on the form of glossary entries: Using =doc:𝒳= will result in the word =𝒳= being printed as a hyperlink to a glossary entry, which you the user will eventually declare using =show:GLOSSARY=; moreover, the glossary entry will also have a link back to where the =doc:𝒳= was declared. E.g., doc:defmacro and doc:lambda. We make a ~show:𝒳~ link type to print the value of the variable =𝒳= as follows, with =GLOSSARY= being a reserved name. #+begin_details Implementation of ‘show’ #+begin_src emacs-lisp (o-deflink show "Yield the value of the expression O-LABEL, with =GLOSSARY= being a reserved name. Example uses: show:user-full-name Note that there is `elisp' links with Emacs, out of the box. However, they only serve to evaluate Lisp expressions; for example, to make “link buttons” that do useful things, as follows. [[elisp:(find-file user-init-file)][Init]] In particular, `elisp' links do not export the value of their expression. That is what we accomplish with this new `show' link type." [:face '(:underline "green") :let (o-value (if (equal o-label "GLOSSARY") (pp-to-string (mapcar #'cl-second o--docs-actually-used)) (pp-to-string (eval (car (read-from-string o-label))))) o-expr (if (equal o-label "GLOSSARY") (concat "GLOSSARY ---i.e., o--docs-actually-used" "\n\nWe erase the glossary not on the first export, but on the second export." "\nThe first export collects all citations, which are used in the second export.") o-label)) :help-echo (format (concat "Upon export, the following will be placed literally" "\n\t%s" "\nWhich is the value of the expression:\n\t%s") o-value o-expr)] (cond ((not (equal o-label "GLOSSARY")) o-value) ;; Otherwise O-LABEL is glossary, which we print in HTML & LaTeX ((equal 'html o-backend) (s-join " " (--map (format "%s" (o-html-export-preserving-whitespace (cl-third it)) (cl-second it)) ;; Ignore duplicates; i.e., entries with the same name/title. (cl-remove-duplicates o--docs-actually-used :test (lambda (x y) (cl-equalp (cl-second x) (cl-second y))))))) (t (s-join "\n\n" (cl-loop for (label name doc) in o--docs-actually-used collect (format (concat "\\vspace{1em}\\phantomsection" "\\textbf{%s}\\quad" "\\label{o-glossary-%s}" "%s See page " "\\pageref{org-special-block-extras" "-glossary-declaration-site-%s}") name o-label (when doc (thread-last doc ;; preserve whitespace (s-replace "&" "\\&") ;; Hack! (s-replace " " " \\quad ") (s-replace "\n" " \\newline{\\color{white}.}"))) o-label)))))) #+end_src #+end_details As an example, we know have generic sentences: | =My name is show:user-full-name and I am using Emacs show:emacs-version ^_^= | |----------------------------------------------------------------------------| | My name is show:user-full-name and I am using Emacs show:emacs-version ^_^ | For example, here is a word whose documentation is obtained from Emacs rather than me writing it: doc:thread-last. If you click on it, in the LaTeX output, you will be directed to the glossary at the end of this article ---glossaries are not printed in HTML rendering. /Neato! The whitespace in the documentation is preserved in the LaTeX output as is the case for HTML./ If we decide to erase a =doc:𝒳=, then it should not longer appear in the printed glossary listing. Likewise, a =documentation= block has its Org markup exported according to the backend being exported to, hence if we decide to switch between different backends then only the first rendition will be used /unless/ we erase the database each time after export. #+begin_src emacs-lisp :noweb-ref enable-mode :tangle no (defvar o--docs-empty! (list nil t) "An indicator of when glossary entries should be erased. We erase the glossary not on the first export, but on the second export. The first export collects all citations, which are used in the second export.") (setcdr (last o--docs-empty!) o--docs-empty!) ;; It's an infinite cyclic list. ;; Actual used glossary entries depends on the buffer; so clean up after each export (advice-add #'org-export-dispatch :after (lambda (&rest _) (when (pop o--docs-empty!) (setq o--docs-actually-used nil ;; The 𝒳 of each “doc:𝒳” that appears in the current buffer. o--docs nil)))) ;; The “#+begin_documentation ⋯ :label 𝒳” of the current buffer. #+end_src # # (-let [sym 'org-export-dispatch] (advice-mapc (lambda (advice _props) (advice-remove sym advice)) sym)) ** Next Steps :PROPERTIES: :CUSTOM_ID: Next-Steps-tooltips :END: # [[color:orange][Going forward,]] Going forward, it'd be nice to have URLs work well upon export for =documentation= block types; whereas they currently break the HTML export. - If an entry is referenced multiple times, such as doc:cat, then it would be nice if the glossary referred to the pages of all such locations rather than just the final one. - The glossary current prints in order of appearance; we may want to have the option to print it in a sorted fashion. - Perhaps use the line activation feature to provide link tooltips immediately rather than rely on exportation. - +The =show= link type could accept an arbitrary Lisp expression as a bracketed link.+ It does ;-) - When one clicks on a =doc= documentation link, it would be nice to ‘jump’ to its associated =#+begin_documentation= declaration block in the current buffer, if possible. * Marginal, “one-off”, remarks :PROPERTIES: :CUSTOM_ID: Marginal-one-off-remarks :END: # Set the width onces-and-for-all using header-args: # # (set-block-header-args margin :main-arg \"Hia\" :width \"not working...!\") #+begin_tiny #+begin_center /(Scroll to make tooltips disappear!)/ #+end_center #+end_tiny The use of ~doc:𝒳~ --- and [[doc:o--documentation][documentation]] --- are for explanatory remarks, personal documentation, or glossary, that is intended to be used in multiple places. However, sometimes we want a “one-off” remark containing, say, references or additional explanation; for that we provide [[doc:o--margin][margin]]. For example: #+begin_quote /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 -------------------------------------------------------------------------------- *Source:* #+begin_src org /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 #+end_src #+end_quote In HTML, such “marginal remarks” appear as tooltips; in LaTeX, they appear in the margin. #+begin_details Tests #+begin_src emacs-lisp :tangle tests.el :comments link (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")) (deftest "It exports into an tooltip" [margin] (⇝ margin " title" [margin] (⇝ margin "title=\"The God of Abraham; known as Elohim" "
        in the Bible\">")) (deftest "The marginal remark appears in a tiny circle" [margin] (⇝ margin "")) #+end_src #+end_details -------------------------------------------------------------------------------- #+begin_details Implementation :title-color pink #+begin_src emacs-lisp (o-defblock margin (marker nil :display 'full :face '(:foreground "grey" :weight bold :underline "orange" :overline "orange")) (color "gray!80" counter "footnote" width "\\paperwidth - \\textwidth - \\oddsidemargin - 1in - 3ex") ;; Width: https://tex.stackexchange.com/a/101861/69371 :please-inline__no-extra-newlines__k-thx-bye! "Produce an HTML tooltip or a LaTeX margin note. The ‘margin’ block is intended for “one-off” (mostly optional) remarks. For notes that you want to use repeatedly, in multiple articles or in multiple locations in the same article, consider using ‘documentation’ to declare them and ‘doc’ to invoke them. For LaTeX, place ‘#+resize:’ to have the remainder of a block be resized, for now 1.3 the margin width ---requires \\usepackage{adjustbox}. ---------------------------------------------------------------------- WIDTH, COUNTER, and COLOR are LaTeX specfic. When no label, marker, is used for a marginal note, we rely on a COUNTER, such as ‘footnote’ (default) or ‘sidenote.’ Since HTML has no margin per se, we use “∘” as default marker: Users hover over it to read the marginal note. Marginal notes have their labels, markers, in black and the notes themselves have COLOR being grey!80. In Emacs, margin links appear grey with an orange tinted boarder. Regarding LaTeX, since verbatim environments do not in general work well as arguments to other commands, such as ‘\\marginpar’, we save the contents of the special block in a ‘minipage’ within a LaTeX ‘box’; then we can unfold such a box in the margin. Hence, ‘src’ blocks can appear within ‘margin’ blocks (•̀ᴗ•́)و The WIDTH argument is the width of the margin; i.e., the width of the underlying minipage. One could use \\maxsizebox{.25\\textwidth}{\\textheight}{ ... } which only resizes the content if its natural size is larger than the given 〈width〉 or 〈height〉. We don't use this, since maxsizebox does not natively allow linebreaks (e.g., one would wrap contents in a tabular environment then use ‘\\\\’, two backslashes, to request a line break; but this crashes if one wants to also use verbatim environments.) In LaTeX, it may be useful to invoke ‘\\dotfill’." (-let [stepcounter (if marker "" (format "\\stepcounter{%s}" counter))] (pcase backend (`latex (setq marker (or marker (format "{\\the%s}" counter))) ;; "\\circ" (format "\\!\\!${}^{\\textnormal{%s}}$ \\newsavebox{\\OrgSpecialBlockExtrasMarginBox} \\begin{lrbox}{\\OrgSpecialBlockExtrasMarginBox} \\begin{minipage}{%s} \\raggedright \\iffalse Otherwise default alignment is fully justified. \\fi \\footnotesize \\setminted{fontsize=\\footnotesize, breaklines} \\iffalse HACK! \\fi \\color{%s} {\\color{black}${}^{\\textnormal{%s}}$}\n\\normalfont\n %s \\end{minipage} \\end{lrbox} \\marginpar{\\usebox{\\OrgSpecialBlockExtrasMarginBox}%s} \\hspace{-1.9ex} \\global\\let\\OrgSpecialBlockExtrasMarginBox\\relax" marker width color marker (if (s-contains? "#+resize:" contents) (s-concat (s-replace "#+resize:" "#+latex: \\maxsizebox{1.3\\textwidth}{\\textheight}{\\begin{tabular}{l}\n" (s-trim contents)) "\n\\end{tabular}}") (s-trim contents)) stepcounter)) (_ (setq marker (or marker "°")) (format "%s " (o-html-export-preserving-whitespace contents) ; MA: FIXME: (org-export-string-as contents 'html :body-only-please) marker))))) #+end_src #+RESULTS: : o-link/margin #+end_details #+begin_details Example: "Links, without and with explict lables" This [[margin:][hola and *woah* ]] works! This [[margin:look!][hola]] works! -------------------------------------------------------------------------------- *Source:* #+begin_src org This [[margin:][hola and *woah* ]] works! This [[margin:look!][hola]] works! #+end_src #+end_details #+begin_details Example: "Middle of a sentence, no explicit label" Hello this #+begin_margin Extra details about whatever it is I'm talking about. #+end_margin is super neat! -------------------------------------------------------------------------------- *Source:* #+begin_src org :tangle no Hello this #+begin_margin Extra details about whatever it is I'm talking about. #+end_margin is super neat! #+end_src #+end_details #+begin_details Example: "Middle of a sentence, explicit (full word) label" Hello #+begin_margin this Extra details about whatever it is I'm talking about. #+end_margin is super neat! -------------------------------------------------------------------------------- *Source:* #+begin_src org :tangle no Hello #+begin_margin this Extra details about whatever it is I'm talking about. #+end_margin is super neat! #+end_src #+end_details #+begin_details "★ Example: A ‘margin’ with an ‘export’ in the middle" :title-color orange An HTML “marquee” #+begin_margin An example is best: #+begin_export html Catch me if you can! #+end_export It is pronounced “mar key”. #+end_margin is a scrolling piece of text. -------------------------------------------------------------------------------- *Source:* #+begin_src org An HTML “marquee” ,#+begin_margin An example is best: ,#+begin_export html Catch me if you can! ,#+end_export It is pronounced “mar key”. ,#+end_margin is a scrolling piece of text. #+end_src #+end_details #+begin_details Example: Brief code snippet in tooltip/margin It's a #+begin_margin simple /Proof Sketch:/ “Squint” your eyes to see ~zero : ℕ~ and ~nothing : Maybe ℕ~ as essentially the same, and “squint” your eyes to see that ~suc~ is essentially the same as ~just~. More formally, here's one direction: #+begin_src haskell :tangle no to : Maybe ℕ → ℕ to nothing = zero to (just n) = suc n #+end_src The rest is an arduous exercise if you don't know what's going on. #+end_margin exercise to show that ℕ is fixedpoint of ~Maybe~. -------------------------------------------------------------------------------- *Source:* #+begin_src org It's a ,#+begin_margin simple /Proof Sketch:/ “Squint” your eyes to see ~zero : ℕ~ and ~nothing : Maybe ℕ~ as essentially the same, and “squint” your eyes to see that ~suc~ is essentially the same as ~just~. More formally, here's one direction: ,#+begin_src haskell :tangle no to : Maybe ℕ → ℕ to nothing = zero to (just n) = suc n ,#+end_src The rest is an arduous exercise if you don't know what's going on. ,#+end_margin exercise to show that ℕ is fixedpoint of ~Maybe~. #+end_src #+end_details #+begin_details "★ Example: Lengthy ‘margin’ with multiple code blocks!" :title-color orange It's a #+begin_margin simple We show that there is an /isomorphism/; i.e., a non-lossy protocol between ~Maybe ℕ~ and ~ℕ~ in stages. First, let's recall the definitions ... in Agda ... #+begin_src haskell :tangle no :tangle no data Maybe (A : Set) : Set₁ where nothing : Maybe A just : A → Maybe A data ℕ : Set where zero : ℕ suc : ℕ → ℕ #+end_src We can show ~Maybe ℕ ≅ ℕ~ by writing two functions... #+begin_src haskell :tangle no to : Maybe ℕ → ℕ to nothing = zero to (just n) = suc n from : ℕ → Maybe ℕ from zero = nothing from (suc n) = just n #+end_src ...and, finally, checking that the two functions undo each other... #+begin_src emacs-lisp :tangle no to∘from : ∀ {n} → to (from n) ≡ n to∘from n = {! try it! } from∘to : ∀ {m} → from (to m) ≡ m from∘to m = {! try it! } #+end_src This is “simple”, but involved! #+end_margin exercise to show that ℕ is fixedpoint of ~Maybe~. -------------------------------------------------------------------------------- *Source:* #+begin_src org :tangle no It's a ,#+begin_margin simple We show that there is an /isomorphism/; i.e., a non-lossy protocol between ~Maybe ℕ~ and ~ℕ~ in stages. First, let's recall the definitions ... in Agda ... ,#+begin_src haskell :tangle no :tangle no data Maybe (A : Set) : Set₁ where nothing : Maybe A just : A → Maybe A data ℕ : Set where zero : ℕ suc : ℕ → ℕ ,#+end_src We can show ~Maybe ℕ ≅ ℕ~ by writing two functions... ,#+begin_src haskell :tangle no to : Maybe ℕ → ℕ to nothing = zero to (just n) = suc n from : ℕ → Maybe ℕ from zero = nothing from (suc n) = just n ,#+end_src ...and, finally, checking that the two functions undo each other... ,#+begin_src emacs-lisp to∘from : ∀ {n} → to (from n) ≡ n to∘from n = {! try it! } from∘to : ∀ {m} → from (to m) ≡ m from∘to m = {! try it! } ,#+end_src This is “simple”, but involved! ,#+end_margin exercise to show that ℕ is fixedpoint of ~Maybe~. #+end_src #+end_details # HTML Setup -- I want the code in the tooltips to look nice :-) # +html: #+html: For the tooltips containing code snippets, I've declared the following to make the code look nice. #+begin_src org :tangle no ,#+html: #+end_src # ,#+html: -------------------------------------------------------------------------------- orange:Going orange:forward, it would be nice to get full code colouring of source blocks in the HTML tooltips ---which can be done using doc:org-export-string-as, just need the time. ** COMMENT margins MWE :PROPERTIES: :CUSTOM_ID: COMMENT-margins-MWE :END: #+title: Margins MWE #+options: toc:nil # HTML Setup -- I want the code in the tooltips to look nice :-) #+html: # LaTeX Setup -- For Unicode & wider margin in the examples; not needed in general! # ############################################################################### # https://armkeh.github.io/unicode-sty/ #+latex_header: \usepackage{\string~"/unicode-sty/unicode"} #+latex_header: \usepackage[includemp, #+latex_header: paperwidth=18.90cm, #+latex_header: paperheight=24.58cm, #+latex_header: top=2.170cm, #+latex_header: bottom=3.510cm, #+latex_header: inner=2.1835cm, #+latex_header: outer=2.1835cm, #+latex_header: hmargin=15mm, vmargin=20mm, #+latex_header: marginparwidth=5cm, #+latex_header: marginparsep=0.4cm]{geometry} # ############################################################################### *** Brief Margin Note :PROPERTIES: :CUSTOM_ID: Brief-Margin-Note :END: # Quran 2:286 /Allah[[margin:][The God of Abraham; known as Elohim in the Bible]] does not burden a soul beyond what it can bear./ *** Long Margin Note :PROPERTIES: :CUSTOM_ID: Long-Margin-Note :END: # Place the following in the margin header to change the width of the margin note. # :width .55\\textwidth It's a #+begin_margin simple We show that there is an /isomorphism/; i.e., a non-lossy protocol between ~Maybe ℕ~ and ~ℕ~ in stages. First, let's recall the definitions ... in Agda ... #+begin_src haskell :tangle no :tangle no data Maybe (A : Set) : Set₁ where nothing : Maybe A just : A → Maybe A data ℕ : Set where zero : ℕ suc : ℕ → ℕ #+end_src We can show ~Maybe ℕ ≅ ℕ~ by writing two functions... #+begin_src haskell :tangle no to : Maybe ℕ → ℕ to nothing = zero to (just n) = suc n from : ℕ → Maybe ℕ from zero = nothing from (suc n) = just n #+end_src ...and, finally, checking that the two functions undo each other... #+begin_src emacs-lisp :tangle no to∘from : ∀ {n} → to (from n) ≡ n to∘from n = {! try it! } from∘to : ∀ {m} → from (to m) ≡ m from∘to m = {! try it! } #+end_src This is “simple”, but involved! #+end_margin exercise to show that ℕ is fixedpoint of ~Maybe~. * Equational Proofs :PROPERTIES: :CUSTOM_ID: Equational-Proofs :END: Sometimes you just want to have a quick, yet /detailed/, explanation and so we can use *[[green:org-lists as equational proofs]]* ---thanks to the [[doc:o--calc][calc]] block! #+begin_details Implementation :title-color pink #+begin_src emacs-lisp (defun o--list-to-calc (lst rel hint-format NL-length color) "Get a result from org-list-to-lisp and render it as a calculational proof. LST is an expression, possibly with a hint and dedicated relation. The expression may contain multiple lines, as may the hints. REL is the default relation in the left-most column. HINT_FORMAT is the formatting string for hints; e.g., \"\\color{maroon}{\\langle\\large\\substack{\\text{ %s }}⟩}\" NL-length is how long the explicit vertical space, \\, should be. The number refers to the vspace occupied nearly by the height of a single normal sized letter. COLOR is the colour of the hints." (cond ((symbolp lst) "") ((symbolp (car lst)) (o--list-to-calc (cadr lst))) (t (-let* (((conclusion₀ children) lst) ((expr₀ hint) (s-split "--" conclusion₀)) ((op₀ expr₁) (cdr (s-match "^\\[\\(.*\\)\\]\\(.*\\)" expr₀))) (op (or op₀ rel)) (expr (or expr₁ expr₀))) (if (not children) (if hint (format "\n %s \\;\\; & \\qquad \\color{%s}{%s} \n \\\\ & \\begin{split}%s\\end{split}" op color ;; the hfill is so that we do not use substack's default ;; centering, but instead left-align justificatiion ;; hints. (format (s-replace "%s" "{\\large\\substack{\\text{ %s } \\hfill\\\\\n}}" hint-format) (s-replace "\n" " } \\hfill\\\\\n\\text{ " (s-replace "\n\n" (s-repeat (* 6 NL-length) "\n $\\,$") (s-trim hint)))) expr) (format "\\begin{split}%s\\end{split} \n" expr)) ;; MA: The following could be improved. (format "\n %s \\;\\; & \\qquad \\color{%s}{%s} \n \\\\ & \\begin{split}%s\\end{split}" op color ;; BEGIN similar as above (format (s-replace "%s" "{\\large\\substack{\\text{ %s } \\hfill\\\\ \\begin{split} & %s \n\\end{split}\\hfill\\\\\n}}" hint-format) (s-replace "\n" " } \\hfill\\\\\n\\text{ " (s-replace "\n\n" (s-repeat (* 6 NL-length) "\n $\\,$") (s-trim hint))) ;; END similar (s-chop-prefix "\\\\" (s-join "\\\\" (--map (format "%s" (o--list-to-calc it rel hint-format NL-length color)) children)))) expr)))))) (o-defblock calc (main-arg) (rel "=" hint-format "\\left[ %s \\right." explicit-vspace 2 color "maroon") "Render an Org-list as an equational proof. Sometimes the notation delimiting justification hints may clash with the domain topic, so we can change the hint format, e.g., to \"\\left\\langle %s \\right\\rangle\". Set HINT-FORMAT to the empty string, \"\", to comment-out all hints in the exported version. The hint is the text immediately after a “--”, if there are multiple such delimiters only the first is shown; this can be useful if we want to have multiple alternatives, say for extra details in the source but not so much in the export. Line breaks are taken literally in the hints, but not in the math; where one uses \\\\. For math with multiple lines, use ‘&’ as an alignment marker; otherwise math is right-justified. For HTML, to use an TeX it must be enclosed in $, since that is what is required by MathJaX." (thread-last (with-temp-buffer (insert raw-contents) (goto-char (point-min)) (org-list-to-lisp)) cdr (--map (format "%s" (o--list-to-calc it rel hint-format explicit-vspace color))) (s-join "\\\\") (format "$$\\begin{align*} & %s \n\\end{align*}$$"))) #+end_src #+RESULTS: | :export | (lambda (label description backend) (s-replace-all `((@@ . )) (o--calc backend (or description label) label :o-link? t))) | :help-echo | (lambda (window object position) (save-excursion (goto-char position) (-let* (((&plist :path :format :raw-link :contents-begin :contents-end) (cadr (org-element-context))) (description (when (equal format 'bracket) (copy-region-as-kill contents-begin contents-end) (substring-no-properties (car kill-ring))))) (format %s | #+end_details #+begin_box "“Programming Comments  ≈  Proof Hints”" :background-color blue Let's prove that $x ≤ z$... #+begin_parallel *Source:* #+begin_src org :tangle no ,#+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 #+end_src #+html:

*Result:* #+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 #+end_parallel Benefits of Org-lists for proofs: + Nested proofs can be /folded-away/ with kbd:TAB. - This provides a nice /proof outline!/ + All Org support for lists active; e.g., [[kbd:M-↑,↓]] for moving /proof steps/ (list items) and [[kbd:M-RET]] for new /proof steps/. + Intentional explanations can make the proof argument more /convincing/, or accurate. Examples below: Rational root equivales perfect power, Handshaking lemma, calculating train length, and last, but not least, “1 + 1 = 2”. -------------------------------------------------------------------------------- Align the proof hints with [[kbd:M-x align-regexp RET -- RET]] ...let's calculate that $a ⊆ e$... #+begin_parallel *Source:* #+begin_src org :tangle no ,#+begin_calc + a + b -- reason for the equality + [⊆] c -- reason for the inclusion + d -- reason for the equality + e -- final justification ,#+end_calc #+end_src #+html:


*Result:* #+begin_calc + a + b -- reason for the equality + [⊆] c -- reason for the inclusion + d -- reason for the equality + e -- final justification #+end_calc #+end_parallel #+end_box #+begin_details Tests #+begin_src emacs-lisp :tangle tests.el :comments link (setq calc (⟰ "#+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")) (deftest "It's an align environment, in displayed math mode" [calc] (⇝ calc "$$\\begin{align*}" (* anything) "\\end{align*}$$")) (deftest "The calculation has 4 proof steps" [calc] ;; The number of steps in a calculation is the number of items in each nesting, minus 1 (at each nesting). ;; Above we have depth 2 with 3 items in each depth, for a total of (3-1) + (3-1) = 2 + 2 = 4. (should (= 4 (s-count-matches (rx (seq "\\" (* whitespace) (any "=" "≤"))) calc)))) (deftest "Of our 4 steps, 3 of them are equalities and one is an inclusion." [calc] (should (= 3 (s-count-matches (rx "= \\;\\;") calc))) (should (= 1 (s-count-matches (rx "≤ \\;\\;") calc)))) (deftest "All of the hints actually appear in the calculational proof" [calc] (mapc (lambda (hint) (should (s-contains? hint calc))) '("Explanation of why $x \\;=\\; y$" "Actually, let me explain:" "hint 1" "hint 2" "Explanation of why $y \\;\\leq\\; z$"))) #+end_src #+end_details -------------------------------------------------------------------------------- Examples... #+begin_details ★ "Proof of “1 + 1 = 2” with source" :title-color orange link-here:1+1=2 #+begin_calc + 1 + 1 + \suc \zero + \suc \zero -- $\def\suc{\mathsf{suc}\,} \def\zero{\mathsf{zero}}$ “1” abbreviates “$\suc \zero$” + \zero + \suc (\suc \zero) -- Addition is defined by moving the $\suc$-s from the left argument to the right. $\def\eqn#1#2{\begin{equation} #2 \tag{#1} \label{#1} \end{equation}}$ That is, we define $x + y$ by considering the possible $shape$ of $x$, which could only be a $\zero$ or a $\suc$-cessor. When $x$ is zero, there are no $\suc$-s to move to the right, so we yield the right. $\eqn{Base Case}{\zero + n \; = \; n}$ When $x$ is a $\suc$, we move the $\suc$ to the right, and continue to try adding. $\eqn{Inductive Step}{\suc m \,+\, n \; = \; m \,+\, \suc n}$ Since we're moving a $\suc$ at each step, eventually we'll hit the base case and be done adding. For now, we apply the \eqref{Inductive Step}. + \suc (\suc \zero) -- Using the \eqref{Base Case} definition of addition. + 2 -- Switching to ‘conventional’ notation #+end_calc -------------------------------------------------------------------------------- *Source:* #+begin_src org :tangle no ,#+begin_calc + 1 + 1 + \suc \zero + \suc \zero -- $\def\suc{\mathsf{suc}\,} \def\zero{\mathsf{zero}}$ “1” abbreviates “$\suc \zero$” + \zero + \suc (\suc \zero) -- Addition is defined by moving the $\suc$-s from the left argument to the right. $\def\eqn#1#2{\begin{equation} #2 \tag{#1} \label{#1} \end{equation}}$ That is, we define $x + y$ by considering the possible $shape$ of $x$, which could only be a $\zero$ or a $\suc$-cessor. When $x$ is zero, there are no $\suc$-s to move to the right, so we yield the right. $\eqn{Base Case}{\zero + n \; = \; n}$ When $x$ is a $\suc$, we move the $\suc$ to the right, and continue to try adding. $\eqn{Inductive Step}{\suc m \,+\, n \; = \; m \,+\, \suc n}$ Since we're moving a $\suc$ at each step, eventually we'll hit the base case and be done adding. For now, we apply the \eqref{Inductive Step}. + \suc (\suc \zero) -- Using the \eqref{Base Case} definition of addition. + 2 -- Switching to ‘conventional’ notation ,#+end_calc #+end_src #+end_details #+begin_details “Calculating” the length of a train link-here:calculating-the-length-of-a-train Let us *green:calculate* the length of a train, that is travelling at a speed of 60 km/hr and, on its way, it crosses a pole in 9 seconds. # Implicitly assuming that it is going straight, no turns or curves. #+begin_calc + \text{ the length of the train } + \text{ the distance from the end of the train to its front } -- Express length as a difference of distance + & \left(\begin{split} &\text{ difference in time from when the train's front} \\ &\text{ crosses the pole until its back crosses the pole } \end{split}\right) \;\times\; \text{speed of the train} -- distance = time × speed + & \text{ how long it takes the train to cross the pole } \;\times\; \text{speed of the train} -- Rephrase + 9 \text{ seconds } \times 60 \text{ km / hr} -- $\def\meters{\,\mathsf{meters}\,} \def\seconds{\,\mathsf{seconds}\,}$ The train crosses the pole in 9 seconds; and the train is travelling at the speed of 60km/hr. + 9 \seconds \times 60 × (1000 / 60 × 60) \meters / \seconds -- Express things in common units, such as seconds; i.e.,: * 1 \text{ km / hr} * 1 \text{ km } /\; 1 \text{ hr } -- Be more explicit * 1000 \meters /\, 1 \text{ hr } -- A kilometre is 1000 meters * 1000 \meters /\, (60 × 60) \seconds -- Express hours in terms of seconds: + 1 \text{ hr } + 60 \,\mathsf{minutes} -- Express in minutes + 60 \,\times\, 1 \,\mathsf{minutes} -- Be more explicit + 60 \,\times\, 60 \,\seconds -- A minute has 60 seconds * (1000 / 60 × 60) \meters / \seconds -- Collecting like-terms Original justification, not shown in export: A kilometre is 1000 meters and an hour is 60 minutes, each having 60 seconds: $1 \text{ km / hr} \;=\; 1 \text{ km } /\; 1 \text{ hr } =\; 1000 \meters /\, (60 × 60) \seconds \;=\; (1000 / 60 × 60) \meters / \seconds$ + 9 \seconds \times (1000 / 60) \meters / \seconds -- Division cancels multiplication: $b × (a / b) = a$ + (1000 / 60) × 9 \seconds × (\meters / \seconds) -- Collect like-terms; i.e., commutativity of × + (1000 / 60) × 9 \meters -- Division cancels multiplication: $b × (a / b) = a$ + 150 \meters -- Arithmetic #+end_calc #+end_details #+begin_details "Euler's Handshaking Lemma" link-here:handshaking-lemma /At a party of 7 people, is it possible that everybody knows exactly 3 other people?/ We could consider all possible cases, which is not particularly enlightening; or we could /calculate/... #+begin_calc :hint-format "\\left[ %s \\right]" + \text{“Obviously”, the sum of everyone's friendship count is twice the total number of friendships} + ∑_p f(p) = 2×F -- Formalise... Let $f(p)$ denote the number of friends that person $p$ has, which is 0 if $p$ has no friends. Let $F$ be the total number of friendships (between any 2 people). Since friendships are always between two people, we have that $\sum_p f(p) = 2 × F$: Counting-up the number of friends that everyone has will always be twice the total number of relationships ---which is always an even number. Formally, * \text{sum of everyone's friendship count} * \sum_p f(p) -- Formalise, where $p$ ranges over people * \sum_p \sum_r 𝑭𝒓_{p, r} -- Definition of $f$: Let $𝑭𝒓_{p, r}$ be 1 exactly when $p$ participates in friendship/relationship $r$, and 0 otherwise, then $f(p) = \sum_r 𝑭𝒓_{p, r}$. * \sum_r \sum_p 𝑭𝒓_{p, r} -- Quantifier interchange; i.e., Fubini's Principle * \sum_r 2 -- Every relationship $r$ is between 2 people; i.e., $\sum_p 𝑭𝒓_{p, r} = 2$ * 2 × \sum_r 1 -- Factoring * 2 × F -- The total number of friendships is $F$ + [⇒] \even{∑_p f(p)} ≡ \even{2 × F} -- Equals for equals; i.e., $if$ those numbers are equal $then$ their parities are equal $\def\even#1{\mathsf{even}\,\left(#1\right)}$ $\def\odd#1{\mathsf{odd}\,\left(#1\right)}$ + \even{∑_p f(p)} -- The right side is clearly true; (‘true’ is the identity of ‘≡’) + {\LARGE ≡}_p \;\even{f(p)} -- $\even{-}$ distributes over sums turning them into equivalences ‘≡’ + \even{ ♯\{p ∣ ¬ \even{f(p)}\} } -- By definition, a chain of equivalences is true exactly when it has an even number of false values + \even{ ♯\{p ∣ \odd{f(p)} \} } -- A non-even number is precisely an odd number + \text{an even number of people have an odd number of friends} -- Un-formalise #+end_calc Back to the problem: At a party of 7 people, is it possible that everybody knows exactly 3 other people? If such a party were possible, the number of people with an odd friend count must be even, but 7 is not even, and so no such party is possible. ( Alternatively, if such a party were possible, then by the Handshaking Lemma, $F = {∑_p f(p) \over 2} = {7 × 3 \over 2} = 10.5$: This is absurd since there cannot be a ‘.5’ relationship! Hence, no such party is possible. ) + Likewise, if exactly 5 people in a room each claim to have shaken 3 hands, then someone is lying. ( Rephrased: The sum of the degrees of the vertices of a (total) doc:graph is twice the number of edges of the graph; moreover, an even number of vertices have an odd degree. The party problem is then: /Is there a graph of 7 nodes with each node being connected to exactly 3 other nodes?/ Apparently not, and we didn't need to draw any graphs at all to prove it. For more on Graph Theory, [[https://ptwiddle.github.io/MAS341-Graph-Theory-2017/][here]] are some lecture notes. ) + [[http://homepage.tudelft.nl/64a8q/papers/handshake.pdf][The power of shaking hands]] has a few non-trivial applications of this lemma. #+end_details #+begin_details ★ Rational root equivales perfect power :title-color orange link-here:rational-root-equivales-perfect-power #+begin_calc :rel ⇔ + \sqrt[n]{k} \text{ is a rational number } + ∃\, a, b •\; \sqrt[n]{k} = {a \over b} -- A rational number is the fraction of two integers. Let variables $a,\, b$ range over integer numbers. + ∃\, a, b •\; k · a ^n = b ^n -- Use arithmetic to eliminate the $n$-th root operator. $\def\exp#1#2{\mathsf{exp}_#1\,#2}$ $\def\eq{\;=\;}$ + ∃\, a, b •\; ∀\, p •\; \exp{p}(k · a ^n) \eq \exp{p}(b ^n ) -- Let $\exp{m} x$ be the number of times that $m$ divides $x$. For example, $\exp{2} 48 \eq 4$ and $\exp{2} 49 \eq 0$. The numbers $p$ with $∀ m : ℤ⁺ \;•\; \exp{m}p \,≠\, 0 \,≡\, m \,=\, p$ are called $prime$ numbers. Let variable $p$ range over prime numbers. Fundamental theorem of arithmetic: Numbers are determined by their prime powers. That is, $\big(∀ \,p\, •\;\; \exp{p} x \eq f(p)\big) \;\;≡\;\; x \eq \big(Π\, p\, •\; p^{f(p)}\big)$ for any $f$. As such, every number is the product of its prime powers: $\qquad x \eq \big(Π \,p\, •\; p^{\exp{p} x}\big)$. And so, any two numbers are the same precisely when they have the same primes: $\qquad x \eq y \;\;≡\;\; \big(∀ p \,•\, \exp{p} x \eq \exp{p} y\big)$. + ∃\, a, b •\; ∀\, p •\; \exp{p} k + n · \exp{p} a \eq n · \exp{p} b -- When $p$ is prime, $\exp{p}(x · y) \eq \exp{p} x \,+\, \exp{p} y$. Aside: In general, $\exp{p}(Π \,i\, \,•\, x_i) \eq (Σ \,i\, \,•\, \exp{p} x_i)$. + ∃\, a, b •\; ∀\, p •\; \exp{p} k \eq n · \Big(\exp{p} b - \exp{p} a\Big) -- Use arithmetic to collect similar terms. + ∀\, p •\; \exp{p} k \text{ is a multiple of } n -- (⇒) is the definition of multiplicity; (⇐) take $a \,≔\, 1$ and define $b$ by its prime powers: $\qquad ∀\, p \,•\, \exp{p} b \,≔\, {\exp{p} k \,/\, n}$ + k \text{ is a perfect $n$-th power; i.e., of the shape } x^n -- Fundamental theorem of arithmetic and definition of ‘perfect’ #+end_calc #+end_details # Not ideal because current design decision favours hard line breaks... # $\begin{align} \label{hi}\zero \,+\, n &\;=\; n \\ (\suc m) \,+\, n &\;=\; \suc (m + n) \end{align}$ # For some reason, I need /some/ math to “activate” MathJax # ${}$ -------------------------------------------------------------------------------- Possibly Useful links: + [[https://www.onemathematicalcat.org/MathJaxDocumentation/TeXSyntax.htm][TeX Commands available in MathJax]] + MathJax crashes if ~\label~ names are not unique ---e.g., for labelled equations, see “1 + 1 = 2” example above. #+begin_details "★ What if I want to do this in LaTeX (e.g., for math.stackexchange.com)?" :title-color orange link-here:equational-proofs-in-LaTeX Copy-paste the following... #+begin_src latex :tangle no :exports code $ % set up \step command \def\BEGINstep{ \left\langle } \def\ENDstep{ \right\rangle } \newcommand{\step}[2][=]{ \\ #1 \;\; & \qquad \color{maroon}{\BEGINstep\text{ #2 } \ENDstep} \\ & } % multi-line step with many lines of text \newcommand{\line}[1]{ \text{#1}\hfill\\ } \newcommand{\stepmany}[2][=]{ \\ #1 \;\; & \qquad \color{maroon}{\BEGINstep \large\substack{ #2 } \ENDstep} \\ & } % multi-line step with 4 lines of text \newcommand{\stepfour}[5][=]{ \stepmany[#1]{\line{#2} \line{#3} \line{#4} \line{#5}} } \newenvironment{calc}{\begin{align*} & }{\end{align*}} $ #+end_src Then, use it as follows... #+begin_src latex :tangle no :exports code \begin{calc} x \step{reason for equality} y \step[\leq]{reason for inclusion} z \end{calc} #+end_src #+end_details * Summary :PROPERTIES: :CUSTOM_ID: Summary :END: #+begin_quote The full article may be read as a [[https://alhassy.github.io/org-special-block-extras/index.pdf][PDF]] or as [[https://alhassy.github.io/org-special-block-extras][HTML]] ---or visit the [[https://github.com/alhassy/org-special-block-extras][repo]]. #+end_quote link-here:summary Let =𝒞= be any of the following: =black=, =blue=, =brown=, =cyan=, =darkgray=, =gray=, =green=, =lightgray=, =lime=, =magenta=, =olive orange=, =pink=, =purple=, =red=, =teal=, =violet=, =white=, =yellow=. | Idea | Documentation | Link only? | |------------------------+-----------------------------+---------------------| | Colours | =𝒞=, [[doc:o--latex-definitions][latex-definitions]], [[doc:o--color][color]] | | | Parallel | [[doc:o--parallel][parallel]] | | | Editorial Comments | [[doc:o--remark][remark]] | | | Folded Details | [[doc:o--details][details]] , [[doc:o--box][box]] | | | Keystrokes | | ~kbd~ | | OctoIcons & Link Here | | ~octoicon~, ~link-here~ | | Documentation-Glossary | [[doc:o--documentation][documentation]] | ~doc~ | | Marginal remarks | [[doc:o--margin][margin]] | | | Badges | [[doc:o-make-badge][badge]] | | | Equational proofs | [[doc:o--calc][calc]] | | Other fun stuff: [[doc:o--solution][solution]], [[doc:o--org-demo][org-demo]], [[doc:o--stutter][stutter]], [[doc:o--rename][rename]], [[doc:o--spoiler][spoiler]], [[doc:o--tree][tree]] :grin: There are also the social badge links: =reddit-subscribe-to=, =github-followers=, =github-forks=, =github-stars, github-watchers=, =twitter-follow=, and =tweet=. # [[color:orange][Going forward,]] it'd be nice to a centralised ‘user manual’ which may be # consulted rather than reading the literate implementation above. ** Installation Instructions :PROPERTIES: :CUSTOM_ID: Installation-Instructions :END: Manually or using [[https://github.com/alhassy/emacs.d#installing-emacs-packages-directly-from-source][quelpa]]: #+BEGIN_SRC emacs-lisp :tangle no ;; ⟨0⟩ Download the org-special-block-extras.el file manually or using quelpa (quelpa '(org-special-block-extras :fetcher github :repo "alhassy/org-special-block-extras")) ;; ⟨1⟩ Have this always active in Org buffers (add-hook #'org-mode-hook #'org-special-block-extras-mode) ;; ⟨1′⟩ Or use: “M-x org-special-block-extras-mode” to turn it on/off #+END_SRC *Or* with [[https://github.com/alhassy/emacs.d#use-package-the-start-of-initel][use-package]]: #+BEGIN_SRC emacs-lisp :tangle no (use-package org-special-block-extras :ensure t :hook (org-mode . org-special-block-extras-mode) :custom ;; The places where I keep my ‘#+documentation’ (o-docs-libraries '("~/org-special-block-extras/documentation.org")) ;; Details heading “flash pink” whenever the user hovers over them? (org-html-head-extra (concat org-html-head-extra "")) ;; The message prefixing a ‘tweet:url’ badge (o-link-twitter-excitement "This looks super neat (•̀ᴗ•́)و:")) #+END_SRC Then, provide support for a new type of special block, say re-using the ~src~ blocks that, say, folds up all such blocks in HTML export, by declaring the following. #+BEGIN_SRC emacs-lisp :tangle no (o-defblock src (lang nil) (title nil exports nil file nil) "Fold-away all ‘src’ blocks as ‘

’ HTML export. If a block has a ‘:title’, use that to title the ‘
’." (format "
%s
 %s 
" (or title (concat "Details; " lang)) raw-contents)) #+END_SRC :Links_from_tdehaeze_reddit: 14 days ago I have made some research, and I could find nice resources: - https://emacs.stackexchange.com/questions/44958/can-i-insert-a-prefix-to-org-babel-source-code-lines-on-export - https://stackoverflow.com/questions/38857751/show-tangled-file-name-in-org-mode-code-block-export - http://kitchingroup.cheme.cmu.edu/blog/2014/09/22/Showing-what-data-went-into-a-code-block-on-export/ - https://lists.gnu.org/archive/html/emacs-orgmode/2016-10/msg00282.html - https://thibaultmarin.github.io/blog/posts/2016-11-13-Personal_website_in_org.html#org306eaa4 Especially the last link were this seems to have been implemented. However it does not use "hooks" but rather define a new export back-end. I'll let you know if I can combine different thing I can find to make something that works. Cheers -------------------------------------------------------------------------------- Context: https://www.reddit.com/r/emacs/comments/k2whsy/declaring_new_special_blocks_with_arguments/gevyoml/?context=3 :End: ** Minimal working example :PROPERTIES: :CUSTOM_ID: Minimal-working-example :END: The following example showcases the prominent features of this library. #+begin_example org ,#+begin_parallel [[color:orange][Are you excited to learn some Lisp?]] [[blue:Yes!]] Pop-quiz: How does doc:apply work? ,#+end_parallel ,#+begin_details Answer link-here:solution Syntactically, ~(apply f '(x0 ... xN)) = (f x0 ... xN)~. [[remark:Musa][Ain't that cool?]] ,#+begin_spoiler aqua That is, [[color:magenta][we can ((apply)) a function to a list of arguments!]] ,#+end_spoiler ,#+end_details #+html:
,#+begin_box octoicon:report Note that kbd:C-x_C-e evaluates a Lisp form! ,#+end_box /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 #+LATEX_HEADER: \usepackage{multicol} #+LATEX_HEADER: \usepackage{tcolorbox} #+latex: In the LaTeX output, we have a glossary. show:GLOSSARY badge:Thanks|for_reading tweet:https://github.com/alhassy/org-special-block-extras badge:|buy_me_a coffee|gray|https://www.buymeacoffee.com/alhassy|buy-me-a-coffee #+end_example Here is what it looks like as HTML (left) and LaTeX (right): #+attr_html: :width 800px #+attr_latex: :width 100px [[file:images/minimal-working-example.png]] #+begin_details Tests #+BEGIN_SRC emacs-lisp :tangle tests.el :comments link ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Run all MWE tests ;; (ert "mwe") (setq mwe (⟰ " ,#+begin_parallel [[color:orange][Are you excited to learn some Lisp?]] [[blue:Yes!]] Pop-quiz: How does doc:apply work? ,#+end_parallel ,#+begin_details Answer link-here:solution Syntactically, ~(apply f '(x0 ... xN)) = (f x0 ... xN)~. [[remark:Musa][Ain't that cool?]] ,#+begin_spoiler aqua That is, [[color:magenta][we can ((apply)) a function to a list of arguments!]] ,#+end_spoiler ,#+end_details ,#+html:
,#+begin_box octoicon:report Note that kbd:C-x_C-e evaluates a Lisp form! ,#+end_box /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 ,#+LATEX_HEADER: \\usepackage{multicol} ,#+LATEX_HEADER: \\usepackage{tcolorbox} ,#+latex: In the LaTeX output, we have a glossary. show:GLOSSARY badge:Thanks|for_reading tweet:https://github.com/alhassy/org-special-block-extras badge:|buy_me_a coffee|gray|https://www.buymeacoffee.com/alhassy|buy-me-a-coffee ")) (deftest "It exports to HTML without any problems" [mwe html-export] (find-file "mwe.org") (should (org-html-export-to-html))) (deftest "It starts with a 2-column div for ‘parallel’" [mwe parallel] (⇝ mwe "
" (* anything) "
")) (deftest "Its initial question is in ‘orange’, with answer in ‘blue’" [mwe color orange blue] (⇝ mwe "Are you excited to learn some Lisp?" (* anything) "Yes!")) (deftest "Its second question, about ‘apply’, has a tooltip" [mwe doc] (⇝ mwe "Pop-quiz: How does " "Then return the value FUNCTION returns." "
Thus, (apply '+ 1 2 '(3 4)) returns 10.

(fn FUNCTION &rest ARGUMENTS)\"" ">apply
work?")) (deftest "Its ‘details’ block is titled “Answer”, in green" [mwe details] (⇝ mwe "" (* anything) ;; styling "" (* anything) "Answer")) (deftest "Its details block begins with an SVG anchor identified as ‘solution’" [mwe link-here] (⇝ mwe "" ;; link-here:solution "" (* anything) "Syntactically, (apply f '(x0 ... xN)) = (f x0 ... xN).")) (deftest "Its top-level remark is my name in a box, then the text, then a closing box delimiter" [mwe remark] (⇝ mwe "" "" "[Musa:" "" " Ain't that cool? " "]" )) (deftest "The aqua-coloured ‘spoiler’ appears within a magenta coloured piece of text" [mwe spoiler color magenta gensym] (⇝ mwe "" (* anything) ;; A random id; e.g., #g289 "{color: aqua; background-color:aqua;}" (* anything) ":hover {color: black; background-color:white;} " "" (* anything) ;; Then it is used "That is, " "we can apply " ;; Here is the spoiler! " a function to a list of arguments!" (* anything) "
")) (deftest "It has a title-less green box starting with an octoicon" [mwe box octoicon kbd] :expected-result :failed ;; FIXME The MWE has been updated, and more tests need to be written. (⇝ mwe "
" (* anything) " C-x C-e evaluates a Lisp form!" (* anything) "
")) (deftest "Its Quranic quote has the user requested tooltip indicated by a small raised circle" [mwe margin] (⇝ mwe "Allah ° " ;; ∘ ! " does not burden a soul beyond what it can bear." (* anything) "Quran 2:286")) (deftest "It concludes with three beautiful badges" [mwe badge] ;; badge:Thanks|for_reading ;; tweet:https://github.com/alhassy/org-special-block-extras ;; badge:|buy_me_a coffee|gray|https://www.buymeacoffee.com/alhassy|buy-me-a-coffee (⇝ mwe "" (* anything) "" (* anything) "")) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; #+END_SRC #+end_details The above section, [[#practice_problems][‘practice problems’]], presents a few puzzles to get you comfortable with ~defblock~ ;-) Other cool stuff... #+attr_html: :width 800px #+attr_latex: :width 100px [[file:images/inference-rules.png]] #+attr_html: :width 800px #+attr_latex: :width 100px [[file:images/calculational_proofs.png]] #+attr_html: :width 800px #+attr_latex: :width 100px [[file:images/marginal_remarks.png]] ** COMMENT Live MWE :ignore: :PROPERTIES: :CUSTOM_ID: Live-MWE :END: #+begin_parallel [[color:orange][Are you excited to learn some Lisp?]] [[blue:Yes!]] Pop-quiz: How does doc:apply work? #+end_parallel #+begin_details Answer link-here:solution Syntactically, ~(apply f '(x0 ... xN)) = (f x0 ... xN)~. [[remark:Musa][Ain't that cool?]] #+begin_spoiler aqua That is, [[color:magenta][we can ((apply)) a function to a list of arguments!]] #+end_spoiler #+end_details #+html:
#+begin_box octoicon:report Note that kbd:C-x_C-e evaluates a Lisp form! #+end_box /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 #+LATEX_HEADER: \usepackage{multicol} #+LATEX_HEADER: \usepackage{tcolorbox} #+latex: In the LaTeX output, we have a glossary. show:GLOSSARY badge:Thanks|for_reading tweet:https://github.com/alhassy/org-special-block-extras badge:|buy_me_a coffee|gray|https://www.buymeacoffee.com/alhassy|buy-me-a-coffee ** Bye! :PROPERTIES: :CUSTOM_ID: Bye :END: badge:thanks|for_reading tweet:https://github.com/alhassy/org-special-block-extras badge:|buy_me_a coffee|gray|https://www.buymeacoffee.com/alhassy|buy-me-a-coffee * Lisp Postamble :noexport: :PROPERTIES: :CUSTOM_ID: Postamble :END: #+BEGIN_SRC emacs-lisp ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (provide 'org-special-block-extras) ;;; org-special-block-extras.el ends here #+END_SRC * COMMENT MELPA Checks :PROPERTIES: :CUSTOM_ID: COMMENT-MELPA-Checks :END: https://github.com/riscy/melpazoid 1. In Github repo: Create new file ⇒ License.txt ⇒ Select template ⇒ GNU 3 2. Ensure first line ends with: -*- lexical-binding: t; -*- 3. Include appropriate standard keywords; #+begin_src emacs-lisp (pp finder-known-keywords) #+end_src #+RESULTS: #+begin_example ((abbrev . "abbreviation handling, typing shortcuts, and macros") (bib . "bibliography processors") (c . "C and related programming languages") (calendar . "calendar and time management tools") (comm . "communications, networking, and remote file access") (convenience . "convenience features for faster editing") (data . "editing data (non-text) files") (docs . "Emacs documentation facilities") (emulations . "emulations of other editors") (extensions . "Emacs Lisp language extensions") (faces . "fonts and colors for text") (files . "file editing and manipulation") (frames . "Emacs frames and window systems") (games . "games, jokes and amusements") (hardware . "interfacing with system hardware") (help . "Emacs help systems") (hypermedia . "links between text or other media types") (i18n . "internationalization and character-set support") (internal . "code for Emacs internals, build process, defaults") (languages . "specialized modes for editing programming languages") (lisp . "Lisp support, including Emacs Lisp") (local . "code local to your site") (maint . "Emacs development tools and aids") (mail . "email reading and posting") (matching . "searching, matching, and sorting") (mouse . "mouse support") (multimedia . "images and sound") (news . "USENET news reading and posting") (outlines . "hierarchical outlining and note taking") (processes . "processes, subshells, and compilation") (terminals . "text terminals (ttys)") (tex . "the TeX document formatter") (tools . "programming tools") (unix . "UNIX feature interfaces and emulators") (vc . "version control") (wp . "word processing")) #+end_example 4. Use #' instead of ' for function symbols 5. Use ‘-’ as a separator, not ‘/’. 6. Consider reading: https://github.com/bbatsov/emacs-lisp-style-guide#the-emacs-lisp-style-guide 7. Use cl-loop, cl-first, cl-second, cl-third instead of loop, first, second, third 8. byte-compile and address any concerns 9. =M-x checkdoc= on the lisp file to ensure it passes expected style issues. - Symbols =nil, t= should not appear in single quotes. 10. Ensure it byte-compiles without any problems. 11. Ensure that package-linter raises no issues; i.e., the following has no result. #+BEGIN_SRC emacs-lisp (use-package package-lint) (-let [it "org-special-block-extras.el"] (ignore-errors (kill-buffer it)) (find-file-other-window it) (package-lint-buffer it)) #+END_SRC 12. Create a recipe file by invoking: M-x package-build-create-recipe - Place it in: melpa/recipes/ - The name of the file should be the name of the package, no extension. #+BEGIN_SRC emacs-lisp :tangle ~/melpa/recipes/org-special-block-extras (org-special-block-extras :fetcher github :repo "alhassy/org-special-block-extras") #+END_SRC 13. Commit and push everything in your project's repo! 14. Ensure the recipe builds successfully: #+BEGIN_SRC shell cd ~/melpa; rm ~/melpa/packages/o-*; make recipes/org-special-block-extras #+END_SRC #+RESULTS: : • Building package org-special-block-extras ... 15. Ensure the package installs properly from within Emacs: #+BEGIN_SRC emacs-lisp (package-install-file "~/melpa/packages/o-20200417.238.el") #+END_SRC #+RESULTS: : #s(package-desc org-special-block-extras (20200417 238) "Twenty-four new custom blocks for Org-mode" ((s (1 12 0)) (dash (2 16 0)) (emacs (24 4))) single nil nil ((:authors ("Musa Al-hassy" . "alhassy@gmail.com")) (:maintainer "Musa Al-hassy" . "alhassy@gmail.com") (:url . "https://alhassy.github.io/org-special-block-extras")) nil) 13. [@13] Produce a dedicated pull request branch #+begin_src emacs-lisp (magit-status "~/melpa") #+end_src + Now =b c= to checkout a new branch. + Push this branch on your melpa fork. + Go to the https://github.com/melpa/ repo and there'll be a big green PR button ^_^ * COMMENT Making ~README.org~ :PROPERTIES: :CUSTOM_ID: COMMENT-Making-README-org :END: Evaluate the following source block with ~C-c C-c~ to produce a ~README~ file. #+NAME: make-readme #+BEGIN_SRC emacs-lisp (with-temp-buffer (insert " ,#+EXPORT_FILE_NAME: README.md #+HTML:

A unified interface for Emacs' Org-mode block & link types (•̀ᴗ•́)و

#+HTML:

Which is used to obtain 30 new custom blocks and 34 link types ¯\\_(ツ)_/¯

,#+OPTIONS: toc:nil d:nil broken-links:t ,#+html:
,#+INCLUDE: ~/org-special-block-extras/org-special-block-extras.org::#Abstract :only-contents t ,#+html:
# +TOC: headlines 2 ,* Installation Instructions ,#+INCLUDE: ~/org-special-block-extras/org-special-block-extras.org::#Installation-Instructions :only-contents t ,* Minimal working example ,#+INCLUDE: ~/org-special-block-extras/org-special-block-extras.org::#Minimal-working-example :only-contents t ,* Bye! ,#+INCLUDE: ~/org-special-block-extras/org-special-block-extras.org::#Bye :only-contents t ") (let ((org-export-use-babel nil) (org-export-with-broken-links t)) (org-mode) (org-md-export-to-markdown))) #+END_SRC #+RESULTS: make-readme : README.md *Then* use =grip= to see that this looks reasonable. * COMMENT Issue ♯8 :Useful_code_to_migrate_to_core:Or_not: :PROPERTIES: :CUSTOM_ID: TODO-HERE :END: To have a unified, and pleasant, interface for declaring new blocks and links, we take the following approach: 0. [@0] ( /Fuse/ the process of link generation and special block support into one macro, [[doc:o-defblock][defblock]] which is like doc:defun. ) 1. The user writes as string-valued function named 𝒳, possibly with arguments, that has access to a ~contents~ and ~backend~ variables. :Posterity_OLD_org_export_parse: (defun org-export (x) "Wrap the given X in an export block for the current backend." (format "\n#+begin_export %s \n%s\n#+end_export\n" (if (equal o--current-backend 'reveal) 'html o--current-backend) x)) (defun o--org-parse (x) "This should ONLY be called within an ORG-EXPORT call." (format "\n#+end_export\n%s\n#+begin_export %s\n" x (if (equal 'reveal o--current-backend) 'html o--current-backend))) :End: #+begin_details ‘defblock’ Implementation #+begin_src emacs-lisp (defvar o--supported-blocks nil "Which special blocks, defined with DEFBLOCK, are supported.") (cl-defmacro o-defblock (name main-arg kwds &rest experimental&&docstring&&body) "Declare a new special block, and link, in the style of DEFUN. A full featured example is at the end of this documentation string. This is an anaphoric macro that provides export support for special blocks *and* links named NAME. Just as an Org-mode src-block consumes as main argument the language for the src block, our special blocks too consume a MAIN-ARG; it may be a symbol or a cons-list consisting of a symbolic name (with which to refer to the main argument in the definition of the block) followed by a default value, then, optionally, any information for a one-time setup of the associated link type. The main arg may be a sequence of symbols separated by spaces, and a few punctuation with the exception of comma ‘,’ since it is a special Lisp operator. In doubt, enclose the main arg in quotes. Then, just as Org-mode src blocks consume key-value pairs, our special blocks consume a number of KWDS, which is a list of the form (key₀ value₀ … keyₙ valueₙ). After that is an optional DOCSTRING, a familar feature of DEFUN. The docstring is displayed as part of the tooltip for the produced link type. Finally, the BODY is a (sequence of) Lisp forms ---no progn needed--- that may refer to the names BACKEND and CONTENTS which refer to the current export backend and the contents of the special block ---or the description clause of a link. CONTENTS refers to an Org-mode parsed string; i.e., Org-markup is acknowledged. In, hopefully, rare circumstances, one may refer to RAW-CONTENTS to look at the fully unparsed contents. Finally, this macro exposes two functions: + ORG-EXPORT: Wrap the argument in an export block for the current backend. + ORG-PARSE: This should ONLY be called within an ORG-EXPORT call, to escape text to Org, and out of the export block. ---------------------------------------------------------------------- TLDR for EXPERIMENTAL&&DOCSTRING&&BODY, the first two parts are optional; they're a symbol, a string, then the main body. The symbol, O-RESPECT-NEWLINES?, when present enables a highly experimental [i.e., do *not* use it!] feature: No new lines for blocks in HTML export. Its need rose from developing the MARGIN block type. ---------------------------------------------------------------------- The relationship between links and special blocks: [ [type:label][description]] ≈ ,#+begin_type label description ,#+end_type ---------------------------------------------------------------------- Example declaration, with all possible features shown: ;; We can use variable values when defining new blocks (setq angry-red '(:foreground \"red\" :weight bold)) (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.\" (format (if (equal backend 'html) \"⟦%s: %s%s⟧\" \"{\\color{%s}\\bfseries %s: %s%s}\") color editor contents signoff)) ;; I don't want to change the definition, but I'd like to have ;; the following as personalised defaults for the “remark” block. ;; OR, I'd like to set this for links, which do not have argument options. (defblock-header-args remark :main-arg \"Jasim Jameson\" :signoff \"( Aim for success! )\") Three example uses: ;; ⟨0⟩ As a special blocks with arguments given. ,#+begin_remark Bobbert Barakallah :signoff \"Thank-you for pointing this out!\" :color green I was trying to explain that ${\large (n × (n + 1) \over 2}$ is always an integer. ,#+end_remark ;; ⟨1⟩ As a terse link, using default values for the args. ;; Notice that Org-mode formatting is recoqgnised even in links. [ [remark:Jasim Jameson][Why are you taking about “$\mathsf{even}$” here?]] ;; ⟨2⟩ So terse that no editor name is provided. [ [remark:][Please improve your transition sentences.]] ;; ⟨★⟩ Unlike 0, examples 1 and 2 will have the default SIGNOFF ;; catenated as well as the default red color. " ;; ⇨ The special block support ;; (add-to-list 'o--supported-blocks name) ;; global var ;; Identify which of the optional features is present... (let* ((optionals (infer-optionals experimental&&docstring&&body)) (o-respect-newlines? (cl-first optionals)) (docstring (cl-second optionals)) (body (caddr optionals))) `(progn ;; Produce an associated Lisp function ,(o-defblock---support-block-type name docstring (if (consp `,main-arg) (car main-arg) 'main-arg) ;; main argument's name (cadr main-arg) ;; main argument's value kwds body ;; MA: I'd like it to be always ‘true’, but it's experimental and breaks so much stuff. o-respect-newlines?) ;; ⇨ The link type support ;; The ‘main-arg’ may contain a special key ‘:link-type’ whose contents ;; are dumped here verbatim. ;; ‘(main-arg-name main-arg-val :face … :follow …)’ (o-deflink ,name [:help-echo (format "%s:%s\n\n%s" (quote ,name) o-label ,docstring) ,@(cddr main-arg) ;; verbatim link extras ] ;; s-replace-all `(("#+end_export" . "") (,(format "#+begin_export %s" backend) . "")) (s-replace-all `(("@@" . "")) ;; (,(format "@@%s:" backend) . "") (,(intern (format "o--%s" name)) o-backend (or o-description o-label) o-label :o-link? t)))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; WHERE ... ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (cl-defun o-defblock---org-export (backend o-link? o-respect-newlines?) "Wrap the given X in an export block for the current backend. This is exposed as ‘org-export’ within ‘defblock’ (via ‘support-block-type’). One can think of this function as replacing the #+begin_𝒳⋯#+end_𝒳 in-place in your Org document; but really that's done by the o--support-special-blocks-with-args." `(lambda (x) (if ,o-link? x ;; o-respect-newlines? is super experimental: It's a bit ugly on the LaTeX side. (cond ((and ,o-respect-newlines? (member (quote ,backend) '(html reveal))) (format "@@%s:%s@@" (quote ,backend) (s-replace "\n" (format "@@\n@@%s:" (quote ,backend)) x) (quote ,backend))) (:else (format "#+begin_export %s\n%s\n#+end_export" (quote ,backend) x)))))) (cl-defun o-defblock---org-parse (backend o-link? o-respect-newlines?) "This should ONLY be called within an ORG-EXPORT call. This is exposed as ‘org-export’ within ‘defblock’ (via ‘support-block-type’). One can think of this function as replacing the #+begin_𝒳⋯#+end_𝒳 in-place in your Org document; but really that's done by the ⋯-support-blocks function." `(lambda (x) (if ,o-link? x (cond ((and ,o-respect-newlines? (member (quote ,backend) '(html reveal))) (format "@@%s@@%s:" x (quote ,backend))) (:else (format "\n#+end_export\n%s\n#+begin_export %s\n" x (quote ,backend))))))) (cl-defmethod o-defblock---support-block-type (name docstring main-arg-name main-arg-value kwds body o-respect-newlines?) "Helper method for o-defblock. This method creates an Org block type's associated Lisp function. NAME, string: The name of the block type. DOCSTRING, string: Documentation of block. MAIN-ARG-NAME: Essentially main-arg's name MAIN-ARG-VALUE: Essentially main-arg's value KWDS, plist: Keyword-value pairs BODY, list: Code to be executed" `(cl-defun ,(intern (format "o--%s" name)) (backend raw-contents &optional ;; ,(car main-arg) ,main-arg-name &rest _ &key (o-link? nil) ,@(-partition 2 kwds)) ,docstring ;; Use default for main argument (when (and ',main-arg-name (s-blank-p ,main-arg-name)) (--if-let (plist-get (cdr (assoc ',name o--header-args)) :main-arg) (setq ,main-arg-name it) (setq ,main-arg-name ,main-arg-value))) (cl-letf (((symbol-function 'org-export) (o-defblock---org-export backend o-link? ,o-respect-newlines?)) ((symbol-function 'org-parse) (o-defblock---org-parse backend o-link? ,o-respect-newlines?))) ;; Use any headers for this block type, if no local value is passed ,@(cl-loop for k in (mapcar #'car (-partition 2 kwds)) collect `(--when-let (plist-get (cdr (assoc ',name o--header-args)) ,(intern (format ":%s" k))) (when (s-blank-p ,k) (setq ,k it)))) (org-export (let ((contents (org-parse raw-contents))) ,@body))))) #+end_src pink:noice [[color:orange][Going forward,]] it would be nice to have a set of switches that apply to all special blocks. For instance, ~:ignore~ to simply bypass the user-defined behaviour of a block type, and ~:noexport~ to zero-out a block upon export. These are super easy to do ---just need a few minutes to breath. It may also be desirable to provide support for [[https://github.com/alhassy/emacs.d#html-folded-drawers][drawers]] ---just as we did to ‘fuse’ the block-type and link-type approaches used here into one macro. Let's have some sanity tests... #+begin_src emacs-lisp :tangle old_tests.el (ert-deftest infer-optionals/specification () (should (equal (list :key "doc" '(1 2 3)) (infer-optionals '(:key "doc" 1 2 3)))) (should (equal (list nil "doc" '(1 2 3)) (infer-optionals '("doc" 1 2 3)))) (should (equal (list nil nil '(1 2 3)) (infer-optionals '(1 2 3)))) (should (equal (list :key nil '(1 2 3)) (infer-optionals '(:key 1 2 3)))) (should (equal (list nil "doc" '(1 2 3)) (infer-optionals '("doc" 1 2 3)))) (should (equal (list nil nil nil) (infer-optionals nil)))) (ert-deftest org-export/spec () (should (equal "x" (funcall (o-defblock---org-export 'html :o-link :respect-newlines) "x"))) (should (equal "x" (funcall (o-defblock---org-export 'html :o-link nil) "x"))) (should (equal "@@html:x@@" (funcall (o-defblock---org-export 'html nil :respect-newlines) "x"))) (should (equal (unindent "#+begin_export html x ,#+end_export") (funcall (o-defblock---org-export 'html nil nil) "x")))) (ert-deftest org-parse/spec () (should (equal "x" (funcall (o-defblock---org-parse 'html :o-link :respect-newlines) "x"))) (should (equal "x" (funcall (o-defblock---org-parse 'html :o-link nil) "x"))) (should (equal "@@x@@html:" (funcall (o-defblock---org-parse 'html nil :respect-newlines) "x"))) (should (equal " ,#+end_export x ,#+begin_export html " (funcall (o-defblock---org-parse 'html nil nil) "x")))) ;; MA: TODO: Test o--support-special-blocks-with-args (⟰) ;; by mocking the evaluation. (deftest "Testing ⟰" :expected-result :failed ;; TODO: Missing tests. (should (equal 1 0))) (defblock hello nil nil "doc" "HELLO") (deftest "Constant blocks export to HTML preserves indentation/enumeration" (-let [o--supported-blocks '(hello)] (should (equal (⟰ "1. item one 2. item two ,#+begin_hello world ,#+end_hello 3. item three") (unindent "1. item one 2. item two ,#+begin_export html HELLO ,#+end_export 3. item three"))))) (deftest "Constant blocks export to LaTeX preserves indentation/enumeration :Pre-processing:" (-let [o--supported-blocks '(hello)] (should (equal (⟰ "1. item one 2. item two ,#+begin_hello world ,#+end_hello 3. item three" 'latex) (unindent "1. item one 2. item two ,#+begin_export latex HELLO ,#+end_export 3. item three"))))) (deftest "Constant blocks export to LaTex preserves indentation/enumeration :Actual_Export:" (should (equal (org-export-string-as (unindent "1. item one 2. item two ,#+begin_hello world ,#+end_hello 3. item three") 'latex :body-only-please) (unindent "\\begin{enumerate} \\item item one \\item item two HELLO \\item item three \\end{enumerate} ")))) (deftest "Constant blocks export to HTML preserves indentation/enumeration :Actual_Export:" (should (equal (org-export-string-as (unindent "1. item one 2. item two ,#+begin_hello world ,#+end_hello 3. item three") 'html :body-only-please) (unindent "
  1. item one
  2. item two

    HELLO
  3. item three
")))) (defblock id nil nil "doc" contents) (deftest "Identity blocks preserve indentation/enumeration :Pre-Processing:" :expected-result :failed ;; TODO: known bug :tags '(core) (should (equal (⟰ "1. item one 2. item two ,#+begin_id world ,#+end_id 3. item three") (unindent "1. item one 2. item two ,#+begin_export html ,#+begin_export html world ,#+end_export ,#+end_export 3. item three")))) (deftest "Identity blocks export to LaTex preserves indentation/enumeration :Actual_Export:" :expected-result :failed ;; TODO: This test points to a known bug. (should (equal (org-export-string-as (unindent "1. item one 2. item two ,#+begin_id world ,#+end_id 3. item three") 'latex :body-only-please) (unindent "\\begin{enumerate} \\item item one \\item item two world \\item item three \\end{enumerate} ")))) (deftest "Identity blocks export to HTML preserves indentation/enumeration :Actual_Export:" :expected-result :failed ;; TODO: This test points to a known bug. (should (equal (org-export-string-as (unindent "1. item one 2. item two ,#+begin_id world ,#+end_id 3. item three") 'html :body-only-please) (unindent "
  1. item one
  2. item two

    world
  3. item three
")))) #+end_src #+end_details 2. [@2] We tell Org to please look at all special blocks #+begin_src org :tangle no ,#+begin_𝒳 main-arg :key₀ value₀ … :keyₙ valueₙ contents ,#+end_𝒳 #+end_src Then, before export happens, to replace all such blocks with the /result/ of calling the user's 𝒳 function; i.e., replace them by, essentially, #+begin_src emacs-lisp :tangle no (𝒳 main-arg :key₀ value₀ … :keyₙ valueₙ) #+end_src #+begin_details Implementing the hooking mechanism The mechanism that rewrites your source... #+begin_src emacs-lisp (defun o--pp-list (xs) "Given XS as (x₁ x₂ … xₙ), yield the string “x₁ x₂ … xₙ”, no parens. When n = 0, yield the empty string “”." (s-chop-suffix ")" (s-chop-prefix "(" (format "%s" (or xs ""))))) (defvar o--current-backend nil "A message-passing channel updated by o--support-special-blocks-with-args and used by DEFBLOCK.") (defun o--support-special-blocks-with-args (backend) "Remove all headlines in the current buffer. BACKEND is the export back-end being used, as a symbol." (setq o--current-backend backend) (let (blk-start ;; The point at which the user's block begins. header-start ;; The point at which the user's block header & args begin. kwdargs ;; The actual key-value arguments for the header. main-arg ;; The first (non-keyed) value to the block. blk-column ;; The column at which the user's block begins. body-start ;; The starting line of the user's block. blk-contents ;; The actual body string. ;; ⟨blk-start/column⟩#+begin_⟨header-start⟩blk main-arg :key₀ val ₀ … :keyₙ valₙ ;; ⟵ ⟨kwdargs⟩ ;; ⟨body-start⟩ body ;; #+end_blk ) (cl-loop for blk in o--supported-blocks do (goto-char (point-min)) (while (ignore-errors (re-search-forward (format "^\s*\\#\\+begin_%s" blk))) ;; MA: HACK: Instead of a space, it should be any non-whitespace, optionally; ;; otherwise it may accidentlly rewrite blocks with one being a prefix of the other! (setq header-start (point)) ;; Save indentation (re-search-backward (format "\\#\\+begin_%s" blk)) (setq blk-start (point)) (setq blk-column (current-column)) ;; actually process body (goto-char header-start) (setq body-start (1+ (line-end-position))) (thread-last (buffer-substring-no-properties header-start (line-end-position)) (format "(%s)") read (--split-with (not (keywordp it))) (setq kwdargs)) (setq main-arg (o--pp-list (car kwdargs))) (setq kwdargs (cadr kwdargs)) (forward-line -1) (re-search-forward (format "^\s*\\#\\+end_%s" blk)) (setq blk-contents (buffer-substring-no-properties body-start (line-beginning-position))) (kill-region blk-start (point)) (insert (eval `(,(intern (format "o--%s" blk)) (quote ,backend) ,blk-contents ,main-arg ,@(--map (list 'quote it) kwdargs)))) ;; The --map is so that arguments may be passed as "this" or just ;; ‘this’ (raw symbols). ;; See: https://github.com/alhassy/org-special-block-extras/issues/8 (indent-region blk-start (point) blk-column) ;; Go back and fix all SRC blocks’ indentation; moreover, we pretty ;; print indent SRCs even if user's did not. (while (and (ignore-errors (re-search-backward (format "^\s*\\#\\+end_src"))) (> (point) blk-start)) (-let [element (org-element-at-point)] ;; (insert (format "%s" 'hi)) (org-with-wide-buffer (org-indent-region (org-element-property :begin element) (org-element-property :end element))))))))) #+end_src Let's have some sanity tests... #+begin_src emacs-lisp :tangle old_tests.el (deftest "pp-list works as desired" (should (equal "1 2 3 4 5" (o--pp-list '(1 2 3 4 5)))) (should (equal "1" (o--pp-list '(1)))) (should (equal "" (o--pp-list nil)))) ;; Using propcheck, we run this test on /arbitrary/ buffer contents. (deftest "No supported blocks means buffer is unchanged" :tags '(core) (let* (o--supported-blocks (propcheck-seed (propcheck-seed)) (buf (propcheck-generate-string nil))) (should (equal buf (with-temp-buffer (insert buf) (o--support-special-blocks-with-args 'html) (buffer-string)))))) (deftest "Constant blocks preserve indentation/enumeration" :expected-result :failed :tags '(core) (defblock go nil nil "doc" "hello") ;; Constantly “hello” (should (equal " 1. item one 2. item two ,#+begin_export html hello ,#+end_export 3. item three" (with-temp-buffer (insert " 1. item one 2. item two ,#+begin_go world ,#+end_go 3. item three") (o--support-special-blocks-with-args 'html) (buffer-string))))) (deftest "Constant blocks export to LaTex preserves indentation/enumeration" (should (equal "\\begin{enumerate} \\item item one \\item item two hello \\item item three \\end{enumerate} " (org-export-string-as " 1. item one 2. item two ,#+begin_go world ,#+end_go 3. item three" 'latex :body-only-please)))) (deftest "Constant blocks export to HTML preserves indentation/enumeration" :expected-result :failed (should (equal "
  1. item one
  2. item two

    hello
  3. item three
" (org-export-string-as " 1. item one 2. item two ,#+begin_go world ,#+end_go 3. item three" 'html :body-only-please)))) (deftest "Identity blocks preserve indentation/enumeration" :expected-result :failed :tags '(core) (defblock id nil nil "doc" contents) (should (equal " 1. item one 2. item two ,#+begin_export html ,#+end_export world ,#+begin_export html ,#+end_export 3. item three" (with-temp-buffer (insert " 1. item one 2. item two ,#+begin_id world ,#+end_id 3. item three") (o--support-special-blocks-with-args 'html) (buffer-string))))) (deftest "Identity blocks export to LaTex preserves indentation/enumeration" :expected-result :failed (defblock id nil nil "doc" contents) (should (equal "\\begin{enumerate} \\item item one \\item item two world \\item item three \\end{enumerate} " (org-export-string-as " 1. item one 2. item two ,#+begin_id world ,#+end_id 3. item three" 'latex :body-only-please)))) (deftest "Identity blocks export to HTML preserves indentation/enumeration" :expected-result :failed (defblock id nil nil "doc" contents) (should (equal "
  1. item one
  2. item two

    world

  3. item three
" (org-export-string-as " 1. item one 2. item two ,#+begin_id world ,#+end_id 3. item three" 'html :body-only-please)))) #+end_src When you enable the ~org-special-block-extras~ mode, it is activated... -------------------------------------------------------------------------------- As it stands, it seems that the contents of a special-block *red:must* have the same indentation as the block header! It green:would be ideal if such indentation could be green:ensured by the ospe setup. TODO: Find the current indentation of the parent block then indent the contents by such offset: Insert the result of the blk call, then invoke an indent of that region to the column of the blk header. #+begin_box *Actually*, it may suffice to just invoke doc:indent-region /without/ providing a given column ;-) #+end_box :Hide: ;; eftest "Non-indented contents do not escape blocks" (defun go () (insert "\n\n") (insert (⟰ (unindent "1. one 2. two #+begin_details NOT ESCAPED #+begin_src emacs-lisp :tangle no (pp-to-string (list x y z)) #+end_src #+end_details 3. three")))) ;; eftest "Non-indented contents do not escape blocks" (defun go2 () (insert "\n\n") (insert (⟰ (unindent "#+begin_stutter 1 1. something\\ this loses indent 2. something else #+begin_remark this loses indent #+end_remark 3. something else #+end_stutter 1. something else #+begin_remark this keeps indent #+end_remark ")))) #+RESULTS: : o--support-special-blocks-with-args :End: # (go) # (go2) #+begin_export html #+end_export 1. something\ this loses indent 2. something else #+begin_export html

[Editor Remark: #+end_export this loses indent #+begin_export html ]

#+end_export 3. something else #+begin_export html #+end_export 1. something else #+begin_export html

[Editor Remark: #+end_export this keeps indent #+begin_export html ]

#+end_export -------------------------------------------------------------------------------- 1. something\\ this loses indent 2. something else #+begin_remark this loses indent #+end_remark 3. something else 4. something else #+begin_remark this keeps indent #+end_remark * Glossary :ignore: :PROPERTIES: :CUSTOM_ID: Glossary :END: #+latex: \section*{Glossary} #+latex: \addcontentsline{toc}{section}{Glossary} show:GLOSSARY * Footnotes :PROPERTIES: :CUSTOM_ID: Footnotes :END: [fn:1] See [[http://www.cs.nott.ac.uk/~pszgmh/fold.pdf][/A tutorial on the universality and expressiveness of fold/]] and [[http://www.cs.ox.ac.uk/people/jeremy.gibbons/publications/urs.pdf][/Unifying Structured Recursion Schemes/]]