#+BEGIN_EXPORT html

soupault Configuration

#+END_EXPORT #+TOC: headlines 2 #+BEGIN_SRC makefile :tangle soupault.mk soupault-build : @echo " run soupault" @soupault serve : soupault-prebuild @echo " start a python server" @cd build; python -m http.server 2>/dev/null theme-build : site/style/plugins.sass ARTIFACTS += build/ #+END_SRC * General Settings #+NAME: prefix #+BEGIN_SRC text ~lthms #+END_SRC #+BEGIN_SRC toml :tangle soupault.conf :noweb tangle [settings] strict = true verbose = false debug = false site_dir = "site" build_dir = "build/<>" page_file_extensions = ["html"] ignore_extensions = [ "draft", "vo", "vok", "vos", "glob", "html~", "org", "aux", "sass", ] generator_mode = true complete_page_selector = "html" default_template = "templates/main.html" content_selector = "main" doctype = "" clean_urls = false #+END_SRC * Widgets ** Setting Page Title #+BEGIN_SRC toml :tangle soupault.conf [widgets.page-title] widget = "title" selector = "h1" default = "~lthms" prepend = "~lthms: " #+END_SRC ** Acknowledging ~soupault~ When creating a new ~soupault~ project (using ~soupault --init~), the default configuration file suggests advertising the use of ~soupault~. Rather than hard-coding the used version of ~soupault~ (which is error-prone), we rather determine the version of ~soupault~ with the following script. #+NAME: soupault-version #+BEGIN_SRC bash :results verbatim output :exports both soupault --version | head -n 1 | tr -d '\n' #+END_SRC The configuration of the widget becomes #+BEGIN_SRC toml :tangle soupault.conf :noweb tangle [widgets.generator-meta] widget = "insert_html" html = """ """ selector = "head" #+END_SRC ** Generating Table of Contents #+BEGIN_SRC toml :tangle soupault.conf [widgets.table-of-contents] widget = "toc" selector = "div#generate-toc" action = "replace_element" min_level = 2 numbered_list = true #+END_SRC ** Rewriting URLs #+BEGIN_SRC lua :tangle plugins/urls-rewriting.lua function prefix_urls (links, attr, prefix_url) index, link = next(links) while index do href = HTML.get_attribute(link, attr) if href then -- remove prefix sometimes introduced by org href = Regex.replace(href, "^file://", "") -- Check if URL starts with a leading "/" if Regex.match(href, "^/") then href = Regex.replace(href, "^/*", "") href = prefix_url .. href HTML.set_attribute(link, attr, href) end end index, link = next(links, index) end end prefix_url = config["prefix_url"] if not prefix_url then prefix_url = "" end if not Regex.match(prefix_url, "^/(.*)") then prefix_url = "/" .. prefix_url end if not Regex.match(prefix_url, "(.*)/$") then prefix_url = prefix_url .. "/" end prefix_urls(HTML.select(page, "a"), "href", prefix_url) prefix_urls(HTML.select(page, "link"), "href", prefix_url) prefix_urls(HTML.select(page, "img"), "src", prefix_url) prefix_urls(HTML.select(page, "script"), "src", prefix_url) #+END_SRC #+BEGIN_SRC toml :tangle soupault.conf :noweb tangle [plugins.urls-rewriting] file = "plugins/urls-rewriting.lua" [widgets.urls-rewriting] widget = "urls-rewriting" prefix_url = "<>" #+END_SRC ** Marking External Links #+BEGIN_SRC lua :tangle plugins/external-urls.lua function mark(name) return '' end links = HTML.select(page, "a") index, link = next(links) while index do href = HTML.get_attribute(link, "href") if href then if Regex.match(href, "^https?://github.com") then icon = HTML.parse(mark('github')) HTML.append_child(link, icon) elseif Regex.match(href, "^https?://") then icon = HTML.parse(mark('external-link')) HTML.append_child(link, icon) end end index, link = next(links, index) end #+END_SRC #+BEGIN_SRC sass :tangle site/style/plugins.sass .url-mark.fa display: inline font-size: 90% width: 1em .url-mark.fa-github::before content: "\00a0\f09b" .url-mark.fa-external-link::before content: "\00a0\f08e" #+END_SRC #+BEGIN_SRC toml :tangle soupault.conf [plugins.external_links] file = "plugins/external-urls.lua" [widgets.mark-external-urls] after = "generate-history" widget = "external_links" #+END_SRC ** Generating Per-File Revisions Tables *** Users Instructions This widgets allows to generate a so-called “revisions table” of the filename contained in a DOM element of id ~history~, based on its history. Paths should be relative to the directory from which you start the build process (typically, the root of your repository). The revisions table notably provides hyperlinks to a ~git~ webview for each commit. For instance, considering the following HTML snippet #+BEGIN_SRC html
site/posts/FooBar.org
#+END_SRC will replace the content of this ~
~ with the revisions table of ~site/posts/FooBar.org~. *** Implementations Details #+BEGIN_TODO This plugin should be reimplemented using ~libgit2~ or other ~git~ libraries, in a language more suitable than bash. #+END_TODO The base of the URL webview for the document you are currently reading —afterwards abstracted with the ~<>~ noweb reference— is #+NAME: repo #+BEGIN_SRC text https://code.soap.coffee/writing/lthms.git #+END_SRC This plugin proceeds as follows: 1. Using an ad-hoc script, it generates a JSON containing for each revision - The subject, date, hash, and abbreviated hash of the related commit - The name of the file at the time of this commit 2. This JSON is passed to a mustache engine (~haskell-mustache~) with a proper template 3. The content of the selected DOM element is replaced with the output of ~haskell-mustache~ This translates in Bash as follows: #+BEGIN_SRC bash :tangle scripts/history.sh :shebang "#!/usr/bin/bash" function main () { local file="${1}" local template="${2}" tmp_file=$(mktemp) generate_json ${file} > ${tmp_file} haskell-mustache ${template} ${tmp_file} rm ${tmp_file} } #+END_SRC The difficult part of this script is the definition of the =generate_json= function. We define the three operations our revisions history script uses: - =jget OBJECT FIELD= :: In an =OBJECT=, get the value of a given =FIELD= - =jset OBJECT FIELD VALIE= :: In an =OBJECT=, set the =VALUE= of a given =FIELD= - =jappend ARRAY VALUE= :: Append a =VALUE= at the end of an =ARRAY= #+BEGIN_EXPORT html
JSON manipulation operators definition #+END_EXPORT We use [[https://stedolan.github.io/jq/][~jq~]] to manipulate JSON data. Since ~jq~ processes JSON from its standard input, we first define a helper =_jq= to deal with JSON from variables seamlessly. #+BEGIN_SRC bash :tangle scripts/history.sh function _jq () { local input="${1}" local filter="${2}" echo "${input}" | jq -jcM "${filter}" } #+END_SRC - *-j* tells ~jq~ not to print a new line at the end of its outputs - *-c* tells ~jq~ to print JSON in a compact format (rather than prettified) - *-M* tells ~jq~ to output monochrome outputs Internally, =jget=, =jset=, and =jappend= are implemented with ~jq~ [[https://stedolan.github.io/jq/manual/#Basicfilters][basic filters]]. #+BEGIN_SRC bash :tangle scripts/history.sh function jget () { local obj="${1}" local field="${2}" _jq "${obj}" ".${field}" } function jset () { local obj="${1}" local field="${2}" local val="${3}" _jq "${obj}" "setpath([\"${field}\"]; ${val})" } function jappend () { local arr="${1}" local val="${2}" _jq "${arr}" ". + [ ${val} ]" } #+END_SRC #+BEGIN_EXPORT html
#+END_EXPORT #+BEGIN_SRC bash :tangle scripts/history.sh function _git () { git --no-pager "$@" } FORMAT='{'\ ' "subject" : "%s",'\ ' "abbr_hash" : "%h",'\ ' "hash" : "%H",'\ ' "date" : "%cs"'\ '}' function generate_json () { local file="${1}" local logs=$(_git log \ --follow \ --pretty=format:"${FORMAT}" \ "${file}") if [ ! $? -eq 0 ]; then exit 1 fi local name="${file}" local revisions='[]' while read -r rev; do rev=$(jset "${rev}" "filename" "\"${name}\"") revisions=$(jappend "${revisions}" "${rev}") local hash=$(jget "${rev}" "hash") local rename=$(previous_name "${name}" "${hash}") if [[ ! -z "${rename}" ]]; then name=${rename} fi done < <(echo "${logs}") jset "$(jset "{}" "file" "\"${file}\"")" \ "history" \ "${revisions}" } #+END_SRC #+BEGIN_SRC bash :tangle scripts/history.sh function previous_name () { local name=${1} local hash=${2} local unfold='s/ *\(.*\){\(.*\) => \(.*\)}/\1\2 => \1\3/' _git show --stat=10000 ${hash} \ | sed -e "${unfold}" \ | grep "=> ${name}" \ | xargs \ | cut -d' ' -f1 } #+END_SRC Everything is defined. We can call =main= now. #+BEGIN_SRC bash :tangle scripts/history.sh main "$(cat)" "${1}" #+END_SRC #+BEGIN_SRC html :tangle templates/history.html :noweb tangle
Revisions

This revisions table has been automatically generated from the git history of this website repository, and the change descriptions may not always be as useful as they should.

You can consult the source of this file in its current version here.

{{#history}} {{/history}}
{{date}} {{subject}} {{abbr_hash}}
#+END_SRC #+BEGIN_SRC sass :tangle site/style/plugins.sass #history table @include margin-centered(2rem) border-top: 2px solid $primary-color border-bottom: 2px solid $primary-color border-collapse: collapse; td border-bottom: 1px solid $primary-color padding: .5em vertical-align: top td.commit font-size: smaller td.commit font-family: 'Fira Code', monospace color: $code-fg-color font-size: 80% white-space: nowrap; #+END_SRC #+BEGIN_SRC toml :tangle soupault.conf [widgets.generate-history] widget = "preprocess_element" selector = "#history" command = 'scripts/history.sh templates/history.html' action = "replace_content" #+END_SRC ** Rendering Equations Offline *** Users instructions Inline equations written in the DOM under the class src_css{.imath} and using the @@html:\LaTeX@@ syntax can be rendered once and for all by ~soupault~. User For instance, ~\LaTeX~ is rendered @@html:\LaTeX@@. Using this widgets requires being able to inject raw HTML in input files. *** Implementation details We will use [[https://katex.org][@@html:\KaTeX@@]] to render equations offline. @@html:\KaTeX@@ availability on most systems is unlikely, but it is part of [[https://www.npmjs.com/package/katex][npm]], so we can define a minimal ~package.json~ file to fetch it automatically. #+BEGIN_SRC json :tangle package.json { "private": true, "devDependencies": { "katex": "^0.11.1" } } #+END_SRC We introduce a Makefile recipe to call ~npm install~. This command produces a file called ~package-lock.json~ that we add to ~GENFILES~ to ensure @@html:\KaTeX@@ will be available when ~soupault~ is called. #+BEGIN_REMARK If ~Soupault.org~ has been modified since the last generation, Babel will generate ~package.json~ again. However, if the modifications of ~Soupault.org~ do not concern ~package.json~, then ~npm install~ will not modify ~package-lock.json~ and its “last modified” time will not be updated. This means that the next time ~make~ will be used, it will replay this recipe again. As a consequence, we systematically ~touch~ ~packase-lock.json~ to satisfy ~make~. #+END_REMARK #+BEGIN_SRC makefile :tangle soupault.mk soupault-prebuild : package-lock.json package-lock.json : package.json @echo " init npm packages" @npm install &>> build.log @touch $@ CONFIGURE += package-lock.json node_modules/ #+END_SRC Once installed and available, @@html:\KaTeX@@ is really simple to use. The following script reads (synchronously!) the standard input, renders it using @@html:\KaTeX@@ and outputs the resut to the standard output. #+BEGIN_TODO This script should be generalized to handle both display and inline mode. Currently, only inline mode is supported. #+END_TODO #+BEGIN_SRC js :tangle scripts/katex.js var katex = require("katex"); var fs = require("fs"); var input = fs.readFileSync(0); var html = katex.renderToString(String.raw`${input}`, { throwOnError: false }); console.log(html) #+END_SRC We reuse once again the =preprocess_element= widget. The selector is ~.imath~ (~i~ stands for inline in this context), and we replace the previous content with the result of our script. #+BEGIN_SRC toml :tangle soupault.conf [widgets.inline-math] widget = "preprocess_element" selector = ".imath" command = "node scripts/katex.js" action = "replace_content" #+END_SRC The @@html:\KaTeX@@ font is bigger than the serif font used for this website, so we reduce it a bit with a dedicated SASS rule. #+BEGIN_SRC sass :tangle site/style/plugins.sass .imath font-size: smaller #+END_SRC