diff options
author | Thomas Letan <lthms@soap.coffee> | 2021-03-28 00:03:41 +0100 |
---|---|---|
committer | Thomas Letan <lthms@soap.coffee> | 2021-03-28 14:19:29 +0200 |
commit | 495f9db0606b0ed09e6fac59dc32de4cdc8c0087 (patch) | |
tree | 82ea5c5e247c664de247a0f3818f393ffdb00067 /site/cleopatra | |
parent | Release of coqffi 1.0.0~beta4 (diff) |
2021 Spring redesign
Diffstat (limited to 'site/cleopatra')
-rw-r--r-- | site/cleopatra/commands.org | 36 | ||||
-rw-r--r-- | site/cleopatra/coq.org | 60 | ||||
-rw-r--r-- | site/cleopatra/dependencies.org | 89 | ||||
-rw-r--r-- | site/cleopatra/literate-programming.org | 27 | ||||
-rw-r--r-- | site/cleopatra/org.org | 183 | ||||
-rw-r--r-- | site/cleopatra/soupault.org | 811 | ||||
-rw-r--r-- | site/cleopatra/theme.org | 722 |
7 files changed, 1106 insertions, 822 deletions
diff --git a/site/cleopatra/commands.org b/site/cleopatra/commands.org new file mode 100644 index 0000000..fbf0430 --- /dev/null +++ b/site/cleopatra/commands.org @@ -0,0 +1,36 @@ +#+TITLE: Adhoc *~cleopatra~* commands + +#+SERIES: ../cleopatra.html +#+SERIES_PREV: ./soupault.html + +In this generation process, we provide adhoc commands to ease the +authoring experience. A given command ~<cmd>~ is implemented as a +~makefile~ rule, and can be called with ~cleopatra <cmd>~. + +#+BEGIN_EXPORT html +<nav id="generate-toc"></nav> +<div id="history">site/cleopatra/commands.org</div> +#+END_EXPORT + +* ~serve~ + + This command spawns a simple HTTP server which allows us to navigate + the website more easily. + + #+begin_src makefile :tangle commands.mk +serve : + @cleopatra echo Spwaning "HTTP server" + @cd out && python -m http.server + #+end_src + +* ~update~ + + This commands updates the various dependencies locally installed to + build this website, such as ~soupault~ for instance. + + #+begin_src makefile :tangle commands.mk +update : + @cleopatra echo "Updating" "OCaml dependencies" + @opam update + @opam upgrade -y + #+end_src diff --git a/site/cleopatra/coq.org b/site/cleopatra/coq.org index 64d1e9d..81f3d27 100644 --- a/site/cleopatra/coq.org +++ b/site/cleopatra/coq.org @@ -1,3 +1,15 @@ +#+TITLE: Authoring Content with Coq + +#+SERIES: ../cleopatra.html +#+SERIES_PREV: ./dependencies.html +#+SERIES_NEXT: ./org.html + +#+BEGIN_EXPORT html +<nav id="generate-toc"></nav> +<div id="history">site/cleopatra/coq.org</div> +#+END_EXPORT + + * Author Guidelines * Under the Hood @@ -13,7 +25,6 @@ COQ_ARTIFACTS := $(COQ_POSTS:.v=.vo) \ coq-build : ${COQ_HTML} -theme-build : site/style/coq.sass soupault-build : coq-build ARTIFACTS += ${COQ_ARTIFACTS} .lia.cache @@ -28,52 +39,9 @@ COQDOCARG := --no-index --charset utf8 --short \ --external "https://compcert.org/doc/html" compcert \ --external "https://lysxia.github.io/coq-simple-io" SimpleIO -%.html : %.v coq.mk - @cleopatra echo Exporting "$*.v" +%.html : %.v coq.mk _opam/init + @cleopatra echo Exporting "$*.v" @coqc ${COQCARG} $< @coqdoc ${COQDOCARG} -d $(shell dirname $<) $< @rm -f $(shell dirname $<)/coqdoc.css #+END_SRC - -#+BEGIN_SRC sass :tangle site/style/coq.sass -div.code - padding-left : 1.5rem - padding-left : 1.5rem - white-space: nowrap - line-height : 140% - -div.code, -span.inlinecode - font-family : 'Fira Code', monospace - color : $monospace-color - font-size : 80% - overflow-x : auto - -div.doc - max-width : $content-width - line-height : 140% - - /* dirty patch to get the code in full page width */ - pre - width : calc(100vw - 2*var(--side-margin)) - -.paragraph - margin-bottom : .8em -#+END_SRC - -#+BEGIN_SRC sass :tangle site/style/coq.sass -.code - a[href] - color : inherit - text-decoration : none - background : #f7f7f7 - padding : .1rem .15rem .1rem .15rem - border-radius : 15% - - .url-mark - display: none -#+END_SRC - -# Local Variables: -# org-src-preserve-indentation: t -# End: diff --git a/site/cleopatra/dependencies.org b/site/cleopatra/dependencies.org new file mode 100644 index 0000000..5a79741 --- /dev/null +++ b/site/cleopatra/dependencies.org @@ -0,0 +1,89 @@ +#+TITLE: Installing Dependencies + +#+SERIES: ../cleopatra.html +#+SERIES_NEXT: ./coq.html + +* OCaml and Coq + + #+caption: Dependencies for Coq articles + #+name: coq-deps + | Package | Version | + |--------------+---------| + | coq | 8.13.1 | + | coq-compcert | 3.8 | + + #+caption: Dependencies for the ~coqffi~ series + #+name: lp-deps + | Package | Version | + |---------------+-------------| + | dune | 2.8.4 | + | coq-coqffi | 1.0.0~beta5 | + | coq-simple-io | 1.5.0 | + + #+caption: Soupault + #+name: soupault-deps + | Package | Version | + |----------+---------| + | soupault | 2.5.0 | + + #+name: deps-listing + #+begin_src emacs-lisp :noweb yes :var coq-deps=coq-deps :var lp-deps=lp-deps :var soupault-deps=soupault-deps :results value raw :exports none +;; We use this Emacs Lisp snippet to generate the list of dependencies +;; we have to install with Opam +(defun fmt-deps (d) + (mapconcat (lambda (d) (format "%s" d)) d ".")) + +(string-join + (append (mapcar 'fmt-deps lp-deps) + (mapcar 'fmt-deps soupault-deps) + (mapcar 'fmt-deps coq-deps)) + " ") + #+end_src + + #+begin_src makefile :tangle dependencies.mk :noweb yes +OCAML_VERSION := 4.11.2 +OCAML := ocaml-base-compiler.${OCAML_VERSION} + +_opam/init : + @cleopatra echo "Creating" "a local Opam switch" + @opam switch create . ${OCAML} --repos default,coq-released || true + @cleopatra echo "Installing" "OCaml dependencies" + @opam install <<deps-listing()>> -y + @touch $@ + +CONFIGURE += _opam + #+end_src + +* Frontend + + #+caption: Frontend dependencies + #+name: frontend-deps + | Package | Version | + |---------------+---------| + | katex | 0.13.0 | + | minify | 7.0.1 | + | normalize.css | 8.0.1 | + + #+name: frontend-listing + #+begin_src emacs-lisp :var frontend-deps=frontend-deps :exports none +;; We use this Emacs Lisp snippet to generate the list of dependencies +;; we have to install with npm +(defun fmt-deps (d) + (format " \"%s\": \"^%s\"" (nth 0 d) (nth 1 d))) + +(string-join (mapcar 'fmt-deps frontend-deps) ",\n") + #+end_src + + #+begin_src json :tangle package.json :noweb yes +{ + "dependencies": { + <<frontend-listing()>> + } +} + #+end_src + + #+begin_src makefile :tangle dependencies.mk :noweb yes +package-lock.json : package.json + @cleopatra echo "Installing" "frontend dependencies" + @npm install + #+end_src diff --git a/site/cleopatra/literate-programming.org b/site/cleopatra/literate-programming.org index be25097..63e0b02 100644 --- a/site/cleopatra/literate-programming.org +++ b/site/cleopatra/literate-programming.org @@ -1,6 +1,8 @@ -#+BEGIN_EXPORT html -<h1>Literate Programming Projects</h1> -#+END_EXPORT +#+TITLE: Literate Programming Projects + +#+SERIES: ../cleopatra.html +#+SERIES_PREV: ./org.html +#+SERIES_NEXT: ./theme.html Literate programming is an interesting exercice. It forces programmers to think about how to present their code for other people to @@ -12,18 +14,27 @@ That being said, literate programming is particularly well-suited for blog posts, since at the very least it provides the tool to enforce the code presented to readers is correct. +#+BEGIN_EXPORT html +<nav id="generate-toc"></nav> +<div id="history">site/cleopatra/literate-programming.org</div> +#+END_EXPORT + +* Tangling + We use Emacs and ~org-mode~ to tangle the literate programming projects present in the ~posts/~ directory of this website. This is done with the following emacs lisp script. #+BEGIN_SRC emacs-lisp :tangle export-lp.el -(cleopatra:configure) ; opinionated configuration provided by cleopatra +;; opinionated configuration provided by cleopatra +(cleopatra:configure) +;; allow the execution of shell block code (org-babel-do-load-languages 'org-babel-load-languages - '((shell . t))) ; allow the execution of shell block code + '((shell . t))) - ;; scan the posts/ directory and tangled it into lp/ +;; scan the posts/ directory and tangled it into lp/ (setq org-publish-project-alist '(("lp" :base-directory "site/posts" @@ -46,6 +57,8 @@ literate-programming-prebuild : ARTIFACTS += lp/ site/posts/deps.svg #+END_SRC +* Building + In the =build= phase, we actually try to compile the tangled projects. As of now, there is only one literate program: [[../posts/CoqffiEcho.org][the Echo server implemented in Coq]] which demonstrates how ~coqffi~ can be used to @@ -54,7 +67,7 @@ implement realistic software projects. #+BEGIN_SRC makefile :tangle literate-programming.mk COQFFI_ARCHIVE := site/files/coqffi-tutorial.tar.gz -coqffi-tutorial-build : literate-programming-prebuild +coqffi-tutorial-build : literate-programming-prebuild _opam/init @cleopatra echo "Building" "coqffi tutorial" @cd lp/coqffi-tutorial; dune build --display quiet @cleopatra echo "Archiving" "coqffi tutorial" diff --git a/site/cleopatra/org.org b/site/cleopatra/org.org index acfd432..829272b 100644 --- a/site/cleopatra/org.org +++ b/site/cleopatra/org.org @@ -1,54 +1,30 @@ -* Author Guidelines - -* Under the Hood +#+TITLE: Authoring Content with ~org-mode~ -#+BEGIN_SRC emacs-lisp :tangle scripts/packages.el -(use-package lua-mode :ensure t :defer t) -(use-package rust-mode :ensure t :defer t) -(use-package sass-mode :ensure t :defer t) -(use-package haskell-mode :ensure t :defer t) -(use-package toml-mode :ensure t :defer t) -(use-package json-mode :ensure t :defer t) -(use-package proof-general :ensure t :defer t) -(use-package tuareg :ensure t :defer t) -#+END_SRC - -#+BEGIN_SRC emacs-lisp :tangle scripts/export-org.el -(cleopatra:configure) +#+SERIES: ../cleopatra.html +#+SERIES_PREV: ./coq.html +#+SERIES_NEXT: ./literate-programming.html -(setq org-html-doctype "html5") -(setq org-html-html5-fancy t) - -(org-babel-do-load-languages - 'org-babel-load-languages - '((shell . t) - (dot . t))) +#+BEGIN_EXPORT html +<nav id="generate-toc"></nav> +<div id="history">site/cleopatra/org.org</div> +#+END_EXPORT -(setq org-html-htmlize-output-type nil) -(setq org-export-with-toc nil) - -(add-to-list 'org-entities-user - '("im" "\\(" nil "<span class=\"imath\">" "" "" "")) -(add-to-list 'org-entities-user - '("mi" "\\)" nil "</span>" "" "" "")) +* Author Guidelines -(org-html-export-to-html nil nil nil t) -#+END_SRC +* Implementation -#+BEGIN_SRC makefile :tangle org.mk +#+begin_src makefile :tangle org.mk EMACS := cleopatra-emacs -ORG_POSTS := $(shell find site/ -name "*.org") -ORG_HTML := $(ORG_POSTS:.org=.html) +ORG_IN := $(shell find site/ -name "*.org") +ORG_OUT := $(ORG_IN:.org=.html) org-prebuild : .emacs -org-build : ${ORG_HTML} +org-build : ${ORG_OUT} -theme-build : site/style/org.sass soupault-build : org-build -org-build : literate-programming-build -ARTIFACTS += ${ORG_HTML} +ARTIFACTS += ${ORG_OUT} CONFIGURE += .emacs EXPORT := --batch \ @@ -68,44 +44,91 @@ INIT := --batch --load="${ROOT}/scripts/packages.el" \ .emacs org.mk @cleopatra echo Exporting "$*.org" @${EMACS} $< ${EXPORT} -#+END_SRC - -#+BEGIN_SRC sass :tangle site/style/org.sass -#text-footnotes - max-width : 35rem - -.footpara - display: inline - margin-left: .2em - -.section-number-2:after, -.section-number-3:after - content: ". " - -.section-number-4, -.section-number-5, -.section-number-6 - display: none - -dl - dd p - margin-top: 0 - -.footnotes - font-size : 1rem - -.org-literate-programming - .org-src-tangled-to:before - content: "\f054" - font : normal normal normal 11px/1 ForkAwesome - - .org-src-tangled-to, - padding-left : 2rem - - .org-src-tangled-to, - .org-src-name - font-family : 'Fira Code', monospace - font-size : 70% - font-weight: bold - color : #444 -#+END_SRC +#+end_src + +#+begin_src emacs-lisp :tangle scripts/packages.el +(use-package ox-tufte :ensure t) +#+end_src + +#+begin_src emacs-lisp :tangle scripts/export-org.el +(cleopatra:configure) + +(org-babel-do-load-languages + 'org-babel-load-languages + '((dot . t) + (shell . t))) + +(setq org-export-with-toc nil + org-html-htmlize-output-type nil + org-export-with-section-numbers nil) + +(add-to-list 'org-entities-user + '("im" "\\(" nil "<span class=\"imath\">" "" "" "")) +(add-to-list 'org-entities-user + '("mi" "\\)" nil "</span>" "" "" "")) + +(defun with-keyword (keyword k) + "Look-up for keyword KEYWORD, and call continuation K with its value." + (pcase (org-collect-keywords `(,keyword)) + (`((,keyword . ,kw)) + (when kw (funcall k (string-join kw " ")))))) + +(defun get-keyword (keyword) + "Look-up for keyword KEYWORD, and returns its value" + (with-keyword keyword (lambda (x) x))) + +(defun get-org-title (path) + "Fetch the title of an Org file whose path is PATH." + (with-temp-buffer + (find-file-read-only path) + (get-keyword "TITLE"))) + +(defun insert-title () + "Insert the title of the article." + (with-keyword + "TITLE" + (lambda (title) + (insert + (format "\n\n@@html:<h1>@@ %s @@html:</h1>@@\n\n" title))))) + +(defun insert-series () + "Insert the series root link." + (with-keyword + "SERIES" + (lambda (series) + (insert "\n\n#+attr_html: :class series\n") + (insert series)))) + +(defun insert-series-prev () + "Insert the series previous article link." + (with-keyword + "SERIES_PREV" + (lambda (series-prev) + (insert "\n\n#+attr_html: :class series-prev\n") + (insert series-prev)))) + +(defun insert-series-next () + "Insert the series next article link." + (with-keyword + "SERIES_NEXT" + (lambda (series-next) + (insert "\n\n#+attr_html: :class series-next\n") + (insert series-next)))) + +(defun insert-nav () + "Insert the navigation links." + (when (get-keyword "SERIES") + (insert "\n\n#+begin_nav\n") + (insert-series) + (insert-series-prev) + (insert-series-next) + (insert "\n\n#+end_nav\n"))) + +(beginning-of-buffer) +(insert-nav) +(insert-title) + +(let ((outfile (org-export-output-file-name ".html")) + (org-html-footnotes-section "<!-- %s --><!-- %s -->")) + (org-export-to-file 'tufte-html outfile nil nil nil t)) +#+end_src diff --git a/site/cleopatra/soupault.org b/site/cleopatra/soupault.org index 452f442..92042c2 100644 --- a/site/cleopatra/soupault.org +++ b/site/cleopatra/soupault.org @@ -1,209 +1,136 @@ -#+BEGIN_EXPORT html -<h1><code>soupault</code> Configuration</h1> -#+END_EXPORT +#+TITLE: ~soupault~ -#+NAME: build-dir -#+BEGIN_SRC text :exports none -build -#+END_SRC +#+SERIES: ../cleopatra.html +#+SERIES_PREV: ./theme.html +#+SERIES_NEXT: ./commands.html -#+NAME: prefix -#+BEGIN_SRC text :exports none -~lthms -#+END_SRC +We use ~soupault~ to build this website[fn::~soupault~ is an awesome +free software project, with a unique approach to static website +generation. You should definitely [[https://soupault.app][check out their website]]!]. + +#+begin_export html +<nav id="generate-toc"></nav> +#+end_export -In a nutshell, the purpose of ~soupault~ is to post-process HTML files -generated by the generation processes of *~cleopatra~* +* Installation -The rest of this document proceeds as follows. We first describe the general -settings of ~soupault~. Then, we enumerate the widgets enabled for this website. -Finally, we provide a proper definition for ~soupault~ the *~cleopatra~* -generation process. + We install ~soupault~ in a local switch. We use a witness file + ~_opam/.init~ to determine whether or not our switch has always been + created during a previous invocation of *~cleopatra~*. -#+TOC: headlines 2 + #+begin_src makefile :tangle soupault.mk +OCAML_VERSION := 4.11.2 +OCAML := ocaml-base-compiler.${OCAML_VERSION} -* ~soupault~ General Settings +CONFIGURE += _opam rss.json +ARTIFACTS += out -The general ~settings~ section of ~soupault.conf~ is fairly basic, and there is -little to say that the -[[https://soupault.neocities.org/reference-manual/#getting-started][“Getting -Started”]] already discuss in length. +soupault-prebuild : _opam/init + #+end_src -We emphasize three things: + Using ~soupault~ is as simple as calling it, without any particular + command-line arguments. + + #+begin_src makefile :tangle soupault.mk +soupault-build : package-lock.json style.min.css + @cleopatra echo "Executing" "soupault" + @soupault + #+end_src -- The ~build_dir~ is set to src_text[:exports code :noweb yes]{<<build-dir>>/<<prefix>>} - in place of simply src_text[:exports code :noweb yes]{<<build-dir>>}. -- The ~ignore_extensions~ shall be updated to take into account artifacts - produces by other *~cleopatra~* generation processes. -- We disable the “clean URLs” feature of ~soupault. This option renames - a HTML files ~foo/bar.html~ into ~foo/bar/index.html~, which means when served - by a HTTP server, the ~foo/bar~ URL will work. The issue we have with this - feature is that the internal links within your websiste needs to take their - /final/ URL into account, rather than their actual name. If one day ~soupault~ - starts rewriting internal URLs when ~clean_url~ is enabled, we might - reconsider using it. + We now describe our configuration file for ~soupault~. + +* Configuration + + #+name: base-dir + #+begin_src verbatim :noweb yes :exports none +~lthms + #+end_src -#+BEGIN_SRC toml :tangle soupault.conf :noweb yes +** Global Settings + + The options of the ~[settings]~ section of a ~soupault~ + configuration are often self-explanatory, and we do not spend too + much time to detaul them. + + #+begin_src toml :tangle soupault.conf :noweb yes [settings] strict = true -verbose = false -debug = false site_dir = "site" -build_dir = "<<build-dir>>/<<prefix>>" - +build_dir = "out/<<base-dir>>" +doctype = "<!DOCTYPE html>" +clean_urls = false +generator_mode = true +complete_page_selector = "html" +default_content_selector = "main" page_file_extensions = ["html"] ignore_extensions = [ - "draft", "vo", "vok", "vos", "glob", - "html~", "org", "aux", "sass", + "v", "vo", "vok", "vos", "glob", + "html~", "org" ] - -generator_mode = true -complete_page_selector = "html" default_template_file = "templates/main.html" -default_content_selector = "main" -doctype = "<!DOCTYPE html>" -clean_urls = false -#+END_SRC - -#+BEGIN_TODO -The list of ignored extensions should be programmatically generated with the -help of *~cleopatra~*. -#+END_TODO - -* Widgets +pretty_print_html = false + #+end_src ** Setting Page Title -We use the “page title” widget to set the title of the webpage based on the -first (and hopefully the only) ~<h1>~ tag of the page. + We use the “page title” widget to set the title of the webpage + based on the first (and hopefully the only) ~<h1>~ tag of the + page. -#+BEGIN_SRC toml :tangle soupault.conf + #+begin_src toml :tangle soupault.conf [widgets.page-title] widget = "title" selector = "h1" default = "~lthms" prepend = "~lthms: " -#+END_SRC + #+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. + 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 + #+NAME: soupault-version + #+begin_src bash :results verbatim output soupault --version | head -n 1 | tr -d '\n' -#+END_SRC + #+end_src -The configuration of the widget ---initially provided by ~soupault~--- becomes -less subject to the obsolescence. + The configuration of the widget ---initially provided by + ~soupault~--- becomes less subject to the obsolescence[fn::That + is, as long as ~soupault~ does not change the output of its + ~--version~ option.]. -#+BEGIN_SRC toml :tangle soupault.conf :noweb yes + #+begin_src toml :tangle soupault.conf :noweb yes [widgets.generator-meta] widget = "insert_html" html = """<meta name="generator" content="<<soupault-version()>>">""" selector = "head" -#+END_SRC - -** Generating Table of Contents - -The ~toc~ widget allows for generating a table of contents for HTML files which -contains a node matching a given ~selector~ (in the case of this document, -~#generate-toc~). - -#+BEGIN_SRC toml :tangle soupault.conf -[widgets.table-of-contents] -widget = "toc" -selector = "#generate-toc" -action = "replace_element" -valid_html = true -min_level = 2 -numbered_list = true -#+END_SRC - -#+BEGIN_TODO -We could propose a patch to ~soupault~'s upstream to add numbering in titles. -#+END_TODO - -** Fixing Org Internal Links - -For some reason, Org prefix internal links to other Org documents with -~file://~. To avoid that, we provide a simple plugin which removes ~file://~ -from the begining of a URL. - -#+BEGIN_TODO -This plugin definition should be part of [[./Contents/Org.org][the ~org~ -generation process]], but that would require to aggregate “subconfig” into a -larger one. -#+END_TODO - -This plugin key component is the =fix_org_urls= function. - -- =fix_org_urls(LIST, ATTR)= :: - Enumerate the DOM elements of =LIST=, and check their =ATTR= attribute. - -#+BEGIN_SRC lua :tangle plugins/fix-org-urls.lua -function fix_org_urls(list, attr) - index, link = next(list) - - while index do - href = HTML.get_attribute(link, attr) - - if href then - href = Regex.replace(href, "^file://", "") - HTML.set_attribute(link, attr, href) - end - - index, link = next(list, index) - end -end -#+END_SRC - -We use this function to fix the URLs of tags known to be subject to Org strange -behavior. For now, only ~<a>~ has been affected. - -#+BEGIN_SRC lua :tangle plugins/fix-org-urls.lua -fix_org_urls(HTML.select(page, "a"), "href") -fix_org_urls(HTML.select(page, "img"), "src") -#+END_SRC - -The configuration of this plugin, and the associated widget, is straightforward. - -#+BEGIN_SRC toml :tangle soupault.conf :noweb tangle -[widgets.fix-org-urls] -widget = "fix-org-urls" -#+END_SRC + #+end_src ** Prefixing Internal URLs -On the one hand, internal links can be absolute, meaning they start with a -leading ~/~, and therefore are relative to the website root. On the other hand, -website (especially static website) can be placed in larger context. For -instance, my personal website lives inside the ~~lthms~ directory of the -~soap.coffee~ domain. + On the one hand, internal links can be absolute, meaning they + start with a leading ~/~, and therefore are relative to the + website root. On the other hand, website (especially static + website) can be placed in larger context. For instance, my + personal website lives inside the ~~lthms~ directory of the + ~soap.coffee~ domain[fn::To my experience in hosting webapps and + websites, this set-up is way harder to get right than I initially + expect.]. -The purpose of this plugin is to rewrite internal URLs which are relative to the -root, in order to properly prefix them. + The purpose of this plugin is to rewrite internal URLs which are relative to the + root, in order to properly prefix them. -From a high-level perspective, the plugin structure is the following. + From a high-level perspective, the plugin structure is the following. -#+BEGIN_SRC lua :tangle plugins/urls-rewriting.lua :noweb no-export -prefix_url = config["prefix_url"] -<<validate_prefix>> + First, we validate the widget configuration. -<<prefix_func>> -<<prefix_calls>> -#+END_SRC - -1. We validate the widget configuration. -2. We propose a generic function to enumerate and rewrite tags which can have - internal URLs as attribute argument. -3. We use this generic function for relevant tags. + #+BEGIN_SRC lua :tangle plugins/urls-rewriting.lua +prefix_url = config["prefix_url"] -#+NAME: validate_prefix -#+BEGIN_SRC lua if not prefix_url then Plugin.fail("Missing mandatory field: `prefix_url'") end @@ -215,10 +142,12 @@ end if not Regex.match(prefix_url, "(.*)/$") then prefix_url = prefix_url .. "/" end -#+END_SRC + #+END_SRC + + Then, we propose a generic function to enumerate and rewrite tags + which can have. -#+NAME: prefix_func -#+BEGIN_SRC lua + #+BEGIN_SRC lua :tangle plugins/urls-rewriting.lua function prefix_urls (links, attr, prefix_url) index, link = next(links) @@ -236,33 +165,35 @@ function prefix_urls (links, attr, prefix_url) index, link = next(links, index) end end -#+END_SRC + #+END_SRC -#+NAME: prefix_calls -#+BEGIN_SRC lua + Finally, we use this generic function for relevant tags. + + #+BEGIN_SRC lua :tangle plugins/urls-rewriting.lua 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 +prefix_urls(HTML.select(page, "use"), "href", prefix_url) + #+END_SRC -Again, configuring soupault to use this plugin is relatively straightforward. -The only important thing to notice is the use of the ~after~ field, to ensure -this plugin is run /after/ the plugin responsible for fixing Org documents URLs. + Again, configuring soupault to use this plugin is relatively + straightforward. -#+BEGIN_SRC toml :tangle soupault.conf :noweb tangle + #+BEGIN_SRC toml :tangle soupault.conf :noweb yes [widgets.urls-rewriting] widget = "urls-rewriting" -prefix_url = "<<prefix>>" -after = "fix-org-urls" -#+END_SRC +prefix_url = "<<base-dir>>" +after = "mark-external-urls" + #+END_SRC ** Marking External Links -#+BEGIN_SRC lua :tangle plugins/external-urls.lua + #+BEGIN_SRC lua :tangle plugins/external-urls.lua function mark(name) - return '<i class="url-mark fa fa-' .. name .. - '" aria-hidden="true"></i>' + return '<span class="icon"><svg><use href="/img/icons.svg#' + .. name .. + '"></use></svg></span>' end links = HTML.select(page, "a") @@ -274,85 +205,101 @@ while index do if href then if Regex.match(href, "^https?://github.com") then - icon = HTML.parse(mark('github')) + icon = HTML.parse(mark("github")) HTML.append_child(link, icon) elseif Regex.match(href, "^https?://") then - icon = HTML.parse(mark('external-link')) + 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" + #+END_SRC -.url-mark.fa-external-link::before - content: "\00a0\f08e" -#+END_SRC - -#+BEGIN_SRC toml :tangle soupault.conf + #+BEGIN_SRC toml :tangle soupault.conf [widgets.mark-external-urls] after = "generate-history" widget = "external-urls" -#+END_SRC + #+END_SRC + +** Generating a Table of Contents + + The ~toc~ widget allows for generating a table of contents for + HTML files which contains a node matching a given ~selector~ (in + the case of this document, ~#generate-toc~). + + #+begin_src toml :tangle soupault.conf +[widgets.table-of-contents] +widget = "toc" +selector = "#generate-toc" +action = "replace_content" +valid_html = true +min_level = 2 +max_level = 3 +numbered_list = false +heading_links = true +heading_link_text = " §" +heading_links_append = true +heading_link_class = "anchor-link" + +[widgets.append-toc-title] +widget = "preprocess_element" +selector = "#generate-toc" +command = 'echo "<h2>Table of Contents</h2> $(cat)"' +after = "table-of-contents" + #+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. + 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 + For instance, considering the following HTML snippet -#+BEGIN_SRC html + #+begin_src html <div id="history"> site/posts/FooBar.org </div> -#+END_SRC + #+end_src -This plugin will replace the content of this ~<div>~ with the revisions table of -~site/posts/FooBar.org~. + This plugin will replace the content of this ~<div>~ with the + revisions table of ~site/posts/FooBar.org~. *** Customization -The base of the URL webview for the document you are currently reading -—afterwards abstracted with the ~<<repo>>~ noweb reference— is + The base of the URL webview for the document you are currently + reading is src_verbatim[:noweb yes :exports code]{<<repo>>}. -#+NAME: repo -#+BEGIN_SRC text + #+name: repo + #+begin_src verbatim :exports none https://code.soap.coffee/writing/lthms.git -#+END_SRC + #+end_src -#+BEGIN_SRC html :tangle templates/history.html :noweb tangle -<details class="history"> + The template used to generate the revision table is the following. + + #+begin_src html :tangle templates/history.html :noweb yes +<details id="history"> <summary>Revisions</summary> <p> This revisions table has been automatically generated - from <a href="<<repo>>">the <code>git</code> history - of this website repository</a>, and the change - descriptions may not always be as useful as they - should. + from <a href="<<repo>>">the + <code>git</code> history of this website repository</a>, and the + change descriptions may not always be as useful as they should. </p> <p> - You can consult the source of this file in its current - version <a href="<<repo>>/tree/{{file}}">here</a>. + You can consult the source of this file in its current version + <a href="<<repo>>/tree/{{file}}">here</a>. </p> - <table> + <table class="fullwidth"> {{#history}} <tr> <td class="date" @@ -362,66 +309,44 @@ https://code.soap.coffee/writing/lthms.git {{#modified}} id="modified-at" {{/modified}} - > - {{date}} - </td> + >{{date}}</td> <td class="subject">{{subject}}</td> <td class="commit"> - <a href="<<repo>>/commit/{{filename}}/?id={{hash}}"> - {{abbr_hash}} - </a> + <a href="<<repo>>/commit/{{filename}}/?id={{hash}}">{{abbr_hash}}</a> </td> </tr> {{/history}} </table> </details> -#+END_SRC - -#+BEGIN_SRC sass :tangle site/style/plugins.sass -table - border-top : 2px solid black - border-bottom : 2px solid black - border-collapse : collapse - width : 35rem - -td - border-bottom : 1px solid black - padding : .5em - -#history .commit - font-size : smaller - font-family : 'Fira Code', monospace - width : 7em - text-align : center -#+END_SRC + #+end_src *** Implementation -We use the built-in [[https://soupault.neocities.org/reference-manual/#widgets-preprocess-element][=preprocess_element=]] to implement, which means we need a -script which gets its input from the standard input, and echoes its output to -the standard input. + We use the built-in [[https://soupault.neocities.org/reference-manual/#widgets-preprocess-element][=preprocess_element=]] to implement, which + means we need a script which gets its input from the standard + input, and echoes its output to the standard input. -#+BEGIN_SRC toml :tangle soupault.conf + #+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 +action = "replace_element" + #+end_src -This plugin proceeds as follows: + 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~ + 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 like this. + This translates in Bash like this. -#+BEGIN_SRC bash :tangle scripts/history.sh :shebang "#!/usr/bin/bash" + #+begin_src bash :tangle scripts/history.sh :shebang "#!/usr/bin/bash" function main () { local file="${1}" local template="${2}" @@ -431,26 +356,27 @@ function main () { haskell-mustache ${template} ${tmp_file} rm ${tmp_file} } -#+END_SRC - -Generating the expected JSON is therefore as simple as: - -- Fetching the logs -- Reading 8 line from the logs, parse the filename from the 6th - line -- Outputing the JSON - -We will use ~git~ to get the information we need. By default, ~git~ -subcommands use a pager when its output is likely to be long. This -typically includes ~git-log~. To disable this behavior, ~git~ exposes -the ~--no-pager~ command. Besides, we also need ~--follow~ and -~--stat~ to deal with file renaming. Without this option, ~git-log~ -stops when the file first appears in the repository, even if this -“creation” is actually a renaming. Therefore, the ~git~ command line -we use to collect our history is - -#+NAME: gitlog -#+BEGIN_SRC bash :tangle scripts/history.sh :noweb yes + #+end_src + + Generating the expected JSON is therefore as simple as: + + - Fetching the logs + - Reading 8 line from the logs, parse the filename from the 6th + line + - Outputing the JSON + + We will use ~git~ to get the information we need. By default, + ~git~ subcommands use a pager when its output is likely to be + long. This typically includes ~git-log~. To disable this + behavior, ~git~ exposes the ~--no-pager~ command. Besides, we + also need ~--follow~ and ~--stat~ to deal with file + renaming. Without this option, ~git-log~ stops when the file + first appears in the repository, even if this “creation” is + actually a renaming. Therefore, the ~git~ command line we use to + collect our history is + + #+name: gitlog + #+begin_src bash :tangle scripts/history.sh :noweb yes function gitlog () { local file="${1}" git --no-pager log \ @@ -459,33 +385,35 @@ function gitlog () { --pretty=format:'%s%n%h%n%H%n%cs%n' \ "${file}" } -#+END_SRC + #+end_src -This function will generate a sequence of 8 lines containing all the -relevant information we are looking for, for each commit, namely: + This function will generate a sequence of 8 lines containing all + the relevant information we are looking for, for each commit, + namely: -- Subject -- Abbreviated hash -- Full hash -- Date -- Empty line -- Change summary -- Shortlog -- Empty line + - Subject + - Abbreviated hash + - Full hash + - Date + - Empty line + - Change summary + - Shortlog + - Empty line -For instance, the =gitlog= function will output the following lines -for the last commit of this very file: + For instance, the =gitlog= function will output the following + lines for the last commit of this very file: -#+BEGIN_SRC bash :results verbatim :exports results :noweb yes + #+begin_src bash :results verbatim :exports results :noweb yes <<gitlog>> gitlog "soupault.org" | head -n8 -#+END_SRC + #+end_src -Among other things, the 6th line contains the filename. We need to -extract it, and we do that with ~sed~. In case of file renaming, we -need to parse something of the form ~both/to/{old => new}~. + Among other things, the 6th line contains the filename. We need + to extract it, and we do that with ~sed~. In case of file + renaming, we need to parse something of the form ~both/to/{old => + new}~. -#+BEGIN_SRC bash :tangle scripts/history.sh :noweb yes + #+begin_src bash :tangle scripts/history.sh :noweb yes function parse_filename () { local line="${1}" local shrink='s/ *\(.*\) \+|.*/\1/' @@ -493,18 +421,18 @@ function parse_filename () { echo ${line} | sed -e "${shrink}" | sed -e "${unfold}" } -#+END_SRC - -The next step is to process the logs to generate the expected JSON. We -have to deal with the fact that JSON does not allow the last item of -an array to be concluded by ",". Besides, we also want to indicate -which commit is responsible for the creation of the file. To do that, -we use two variables: =idx= and =last_entry=. When =idx= is equal to -0, we know it is the latest commit. When =idx= is equal to -=last_entry=, we know we are looking at the oldest commit for that -file. - -#+BEGIN_SRC bash :tangle scripts/history.sh :noweb yes + #+end_src + + The next step is to process the logs to generate the expected + JSON. We have to deal with the fact that JSON does not allow the + last item of an array to be concluded by ",". Besides, we also + want to indicate which commit is responsible for the creation of + the file. To do that, we use two variables: =idx= and + =last_entry=. When =idx= is equal to 0, we know it is the latest + commit. When =idx= is equal to =last_entry=, we know we are + looking at the oldest commit for that file. + + #+begin_src bash :tangle scripts/history.sh :noweb yes function generate_json () { local input="${1}" local logs="$(gitlog ${input})" @@ -562,11 +490,11 @@ function generate_json () { echo -n "]}" } -#+END_SRC + #+end_src -Generating the JSON object for a given commit is as simple as + Generating the JSON object for a given commit is as simple as -#+BEGIN_SRC bash :tangle scripts/history.sh :noweb yes + #+begin_src bash :tangle scripts/history.sh :noweb yes function output_json_entry () { local subject="${1}" local abbr_hash="${2}" @@ -585,66 +513,31 @@ function output_json_entry () { echo -n ",\"filename\":\"${file}\"" echo -n "}" } -#+END_SRC + #+end_src -And we are done! We can safely call the =main= function to generate -our revisions table. + And we are done! We can safely call the =main= function to generate + our revisions table. -#+BEGIN_SRC bash :tangle scripts/history.sh + #+begin_src bash :tangle scripts/history.sh main "$(cat)" "${1}" -#+END_SRC + #+end_src ** Rendering Equations Offline *** Users instructions -Inline equations written in the DOM under the class src_css{.imath} and using -the \im \LaTeX \mi syntax can be rendered once and -for all by ~soupault~. User For instance, ~<span class="imath">\LaTeX</span>~ is -rendered \im \LaTeX \mi as expected. + Inline equations written in the DOM under the class + src_css{.imath} and using the \im \LaTeX \mi syntax can be + rendered once and for all by ~soupault~. User For instance, + ~<span class="imath">\LaTeX</span>~ is rendered \im \LaTeX \mi as + expected. -Using this widgets requires being able to inject raw HTML in input files. + Using this widgets requires being able to inject raw HTML in + input files. *** Implementation -We will use [[https://katex.org][\im \KaTeX \mi]] to render equations offline. \im \KaTeX \mi -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 \im \KaTeX -\mi will be available when ~soupault~ is called. - -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~. - -#+BEGIN_SRC makefile :tangle katex.mk -package-lock.json : package.json - @cleopatra echo "Fetching" "npm packages" - @npm install &>> build.log - @touch $@ - -CONFIGURE += package-lock.json node_modules/ -#+END_SRC - -Once installed and available, \im \KaTeX \mi is really simple to use. The -following script reads (synchronously!) the standard input, renders it using \im -\KaTeX \mi and outputs the resut to the standard output. - -#+BEGIN_SRC js :tangle scripts/katex.js + #+begin_src js :tangle scripts/render-equations.js var katex = require("katex"); var fs = require("fs"); var input = fs.readFileSync(0); @@ -656,102 +549,160 @@ var html = katex.renderToString(String.raw`${input}`, { }); console.log(html) -#+END_SRC + #+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. + 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 + #+begin_src toml :tangle soupault.conf [widgets.inline-math] widget = "preprocess_element" selector = ".imath" -command = "node scripts/katex.js" +command = "node scripts/render-equations.js" action = "replace_content" [widgets.display-math] widget = "preprocess_element" selector = ".dmath" -command = "DISPLAY=1 node scripts/katex.js" +command = "DISPLAY=1 node scripts/render-equations.js" action = "replace_content" -#+END_SRC + #+end_src + +** RSS Feed + + #+begin_src toml :tangle soupault.conf +[index] +index = true +dump_json = "rss.json" +extract_after_widgets = ["urls-rewriting"] + +[index.fields] +title = { + selector = ["h1"] +} -The \im\KaTeX\mi font is bigger than the serif font used for this -website, so we reduce it a bit with a dedicated SASS rule. +modified-at = { + selector = ["#modified-at"] +} -#+BEGIN_SRC sass :tangle site/style/plugins.sass -.imath, .dmath - font-size : smaller +created-at = { + selector = ["#created-at"] +} + #+end_src -.dmath - text-align : center -#+END_SRC +** Series Navigation -* *~cleopatra~* Generation Process Definition + #+begin_src lua :tangle plugins/series.lua +function get_title_from_path (path) + if Sys.is_file(path) then + local content_raw = Sys.read_file(path) + local content_dom = HTML.parse(content_raw) + local title = HTML.select_one(content_dom, "h1") -We introduce the ~soupault~ generation process, obviously based on the -[[https://soupault.neocities.org/][~soupault~ HTML processor]]. The structure of -a *~cleopatra~* generation process is always the same. + if title then + return String.trim(HTML.inner_html(title)) + else + Plugin.fail(path .. ' has no <h1> tag') + end + else + Plugin.fail(path .. ' is not a file') + end +end + #+end_src + + #+begin_src lua :tangle plugins/series.lua +function generate_nav_item_from_title (title, url, template) + local env = {} + env["url"] = url + env["title"] = title + local new_content = String.render_template(template, env) + return HTML.parse(new_content) +end + #+end_src -#+BEGIN_SRC makefile :tangle soupault.mk :noweb no-export -<<stages>> -<<dependencies>> -<<ad-hoc-cmds>> -#+END_SRC + #+begin_src lua :tangle plugins/series.lua +function generate_nav_items (cwd, cls, template) + local elements = HTML.select(page, cls) -In the rest of this section, we define these three components. + local i = 1 + while elements[i] do + local element = elements[i] + local url = HTML.strip_tags(element) + local path = Sys.join_path(cwd, url) + local title_str = get_title_from_path(path) -** Build Stages + HTML.replace_content( + element, + generate_nav_item_from_title(title_str, url, template) + ) -From the perspective of *~cleopatra~*, it is a rather simple component, since -the ~build~ stage is simply a call to ~soupault~, whose outputs are located in a -single (configurable) directory. + i = i + 1 + end +end + #+end_src -#+NAME: stages -#+BEGIN_SRC makefile :noweb yes -soupault-build : - @cleopatra echo Running soupault - @soupault + #+begin_src lua :tangle plugins/series.lua +cwd = Sys.dirname(page_file) -ARTIFACTS += <<build-dir>>/ -#+END_SRC +home_template = 'This article is part of the series “<a href="{{ url }}">{{ title }}</a>.”' +nav_template = '<a href="{{ url }}">{{ title }}</a>' -** Dependencies +generate_nav_items(cwd, ".series", home_template) +generate_nav_items(cwd, ".series-prev", nav_template) +generate_nav_items(cwd, ".series-next", nav_template) + #+end_src -Most of the generation processes (if not all of them) need to declare themselves -as a prerequisite for ~soupault-build~. If they do not, they will likely be -executed after ~soupault~ is called. +#+begin_src toml :tangle soupault.conf +[widgets.series] +widget = "series" +#+end_src -This file defines an auxiliary SASS sheet that needs to be declared as a -dependency of the build stage of the [[./Theme.org][~theme~ generation -process]]. +** Injecting Minified CSS -Finally, the offline rendering of equations requires \im \KaTeX \mi to be -available, so we include the ~katex.mk~ file, and make ~package-lock.json~ (the -proof that ~npm install~ has been executed) a prerequisite of ~soupault-build~. + #+begin_src lua :tangle plugins/css.lua +style = HTML.select_one(page, "style") -#+NAME: dependencies -#+BEGIN_SRC makefile -theme-build : site/style/plugins.sass -include katex.mk -soupault-build : package-lock.json -#+END_SRC +if style then + css = HTML.create_text(Sys.read_file("style.min.css")) + HTML.replace_content(style, css) +end + #+end_src -** Ad-hoc Commands + #+begin_src toml :tangle soupault.conf +[widgets.css] +widget = "css" + #+end_src -Finally, this generation process introduces a dedicated (~PHONY~) command to -start a HTTP server in order to navigate the generated website from a browser. +** Cleaning-up -#+NAME: ad-hoc-cmds -#+BEGIN_SRC makefile :noweb yes -serve : - @echo " start a python server" - @cd <<build-dir>>; python -m http.server 2>/dev/null + #+begin_src lua :tangle plugins/clean-up.lua +function remove_if_empty(html) + if String.trim(HTML.inner_html(html)) == "" then + HTML.delete(html) + end +end + #+end_src + + #+begin_src lua :tangle plugins/clean-up.lua +function remove_all_if_empty(cls) + local elements = HTML.select(page, cls) + + local i = 1 + while elements[i] do + local element = elements[i] + remove_if_empty(element) + i = i + 1 + end +end + #+end_src -.PHONY : serve -#+END_SRC + #+begin_src lua :tangle plugins/clean-up.lua +remove_all_if_empty("p") -- introduced by org-mode +remove_all_if_empty("div.code") -- introduced by coqdoc + #+end_src -This command does not assume anything about the current state of generation of -the project. In particular, it does not check whether or not the ~<<build-dir>>~ -directory exists. The responsibility to use ~make serve~ in a good setting lies -with final users. +#+begin_src toml :tangle soupault.conf +[widgets.clean-up] +widget = "clean-up" +#+end_src diff --git a/site/cleopatra/theme.org b/site/cleopatra/theme.org index dce9520..9041be8 100644 --- a/site/cleopatra/theme.org +++ b/site/cleopatra/theme.org @@ -1,267 +1,471 @@ -#+BEGIN_EXPORT html -<h1>Theming and Templating</h1> -#+END_EXPORT +#+TITLE: Layout and Style -The purpose of this build process is to implement the default layout of this -website. To that end, it provides a template file (~main.html~), and a -collection of SASS files that *~cleopatra~* then compiles into a single CSS file -using ~sassc~. +#+SERIES: ../cleopatra.html +#+SERIES_PREV: ./literate-programming.html +#+SERIES_NEXT: ./soupault.html -We first discuss the structure of the website, by defining the default HTML -template =soupault= will use to generate the website. Then, we specify its -minimal design, implemented in SASS. Finally, we discuss the necessary build -instructions for *~cleopatra~*. +#+BEGIN_EXPORT html +<nav id="generate-toc"></nav> +<div id="history">site/cleopatra/theme.org</div> +#+END_EXPORT -* Main HTML Template +* Setup + + As often when it comes to frontend development, we will use several + tools hosted in the ~npm~ packages repository. ~npm~ is infamous + for downloading lots of files and to store it in the ~node_modules/~ + directory. We configure *~cleopatra~* accordingly. + + #+begin_src makefile :tangle theme.mk +CONFIGURE += package.json package-lock.json node_modules + #+end_src + +* Base CSS + + We know construct piece by piece the “base” CSS layout which we will + inject inside a ~<style>~ tag in each web page. + +** Layout + + Our goal is to have a three columns layout: one aside menu, with + the top-level navigation links (technical articles, news, etc.) and + the table of contents of the current pages if relevant, one main + area for the webpage content, and a margin column with side notes + and margin notes. + + #+begin_src css :tangle style.css +:root { + --main-width: 30rem; + --gutter-width: 5rem; + --margin-width: 15rem; + --body-width: calc(var(--main-width) + var(--gutter-width) + var(--margin-width)); +} + +@media (max-width: 55rem) { + :root { + --body-width: var(--main-width); + } +} + #+end_src + + #+begin_src css :tangle style.css +,* { + box-sizing: border-box; +} + +.fullwidth { + width: var(--body-width); +} + +html { + font-size: 1.1rem; +} + +body { + line-height: 1.3; + max-width: var(--body-width); + margin-left: auto; + margin-right: auto; +} + +aside { + background: var(--bg); + z-index: 9999; + width: var(--body-width); + align-self: flex-start; + position: sticky; + top: 0; +} + +aside nav { + text-align: center; + border-bottom: 1px solid var(--fade); +} + +aside nav ul { + list-style: none; + padding: 1rem 0; + margin: 0; +} + +aside nav li { + display: inline; +} + +aside nav li:not(:first-of-type)::before { + content: " · "; +} + +main { + counter-reset: sidenote-counter; + max-width: var(--main-width); +} + +main nav { + font-style: italic; + font-size: smaller; + color: var(--fg-plus); + background: var(--current-line); + padding: .5rem 1rem; +} + +main nav .series-next { + text-align: right; +} + +main nav p.series-next::after { + content: " →"; +} + +main nav p.series-prev::before { + content: "← "; +} + +img { + max-width: 100%; +} + +.sidenote, +.marginnote { + font-size: smaller; + float: right; + clear: right; + margin-right: calc(-1 * (var(--margin-width) + var(--gutter-width))); + width: var(--margin-width); + position: relative; +} + +input.margin-toggle { + display: none; +} + +label.sidenote-number { + display: inline; +} + +label.margin-toggle:not(.sidenote-number) { + display: none; +} + +.sidenote-number:after, +.sidenote:before { + position: relative; + vertical-align: baseline; +} + +.sidenote-number { + counter-increment: sidenote-counter; +} + +.sidenote-number::after { + content: "(" counter(sidenote-counter, lower-greek) ")"; + font-size: 60%; + top: -0.4rem; + left: 0.1rem; +} + +.sidenote::before { + content: "(" counter(sidenote-counter, lower-greek) ")"; + font-size: 70%; + top: -0.5rem; + right: 0.1rem; +} + +div.code, +pre { + width: var(--body-width); + overflow-x: auto; + overflow-y: hidden; + padding: 1rem 2rem; +} + +main { + padding-top: 4.2rem; +} + +h1 { + text-align: center; +} + +h2, h3, h4 { + font-style: italic; +} + +h1, h2, h3, h4 { + font-weight: normal; +} + +dt { + font-weight: bold; +} + +div.code, +span.inlinecode, +code, +.doc pre, +tt, +.dmath, +.imath { + font-family: monospace; + font-size: 80%; +} + +details { + margin: 1.5rem 0; +} + +table { + border-top: 2px solid var(--fg); + border-bottom: 2px solid var(--fg); + border-collapse: collapse; + width: 100%; + margin: 1.5rem 0; +} + +th { + font-weight: normal; + text-transform: uppercase; +} + +td, +th { + border-top: 1px solid var(--fade); + height: 2em; + padding: 0 1em; +} + +td.date, +td.commit { + text-align: center; + font-size: 0.75em; + font-family: monospace; +} + +/* max-width has to be equal to --body-width */ +@media (max-width: 55rem) { + body { + padding: 2rem; + margin: auto; + display: block; + } + + aside { + width: var(--main-width); + margin: auto; + } + + label.margin-toggle:not(.sidenote-number) { + display: inline; + } + + .sidenote, + .marginnote { + display: none; + } + + .margin-toggle:checked + .sidenote, + .margin-toggle:checked + .marginnote { + display: block; + float: left; + left: 1rem; + clear: both; + width: 95%; + margin: 1rem 2.5%; + vertical-align: baseline; + position: relative; + } + + label { + cursor: pointer; + } + + pre, aside { + width: 100%; + } +} + #+end_src + +** Colors + + #+begin_src css :tangle style.css +:root { + --bg: white; + --bg-plus: #f9f8f4; + --current-line: #fbfbfb; + --fade: #cfcecb; + --fg: #3c3c3c; + --fg-plus: #575757; + --doc: #ff4c99; + --warning: #bd745e; + --red: #b3534b; + --green: #6d9319; + --yellow: #d4b100; +} + #+end_src + + #+begin_src css :tangle style.css +body { + font-family: serif; + color: var(--fg); + background: var(--bg); +} + +a[href] { + color: inherit; + text-decoration-color: var(--doc); +} + +h2 a.anchor-link, +h3 a.anchor-link, +h4 a.anchor-link { + display: none; + font-style: normal; + text-decoration: none; + font-family: monospace; + font-size: smaller; + color: var(--doc); +} + +[id] { + scroll-margin-top: 4rem; +} + +h2:hover a.anchor-link, +h3:hover a.anchor-link, +h4:hover a.anchor-link { + display: inline; +} + +.sidenote, +.marginnote { + color: var(--fg-plus); +} + +.sidenote-number:after, +.sidenote:before, +pre, +code, +div.code, +span.inlinecode, +tt { + color: var(--doc); +} + #+end_src + +** Coq + + #+begin_src css :tangle style.css +div.code { + white-space: nowrap; +} + +div.code, +span.inlinecode { + font-family : monospace; +} + +.paragraph { + margin-bottom : .8em; +} + +.code a[href] { + color : inherit; + text-decoration : none; + background : var(--bg-plus); + padding : .1rem .15rem .1rem .15rem; + border-radius : 15%; +} + +.code .icon { + display: none; +} +#+END_SRC -#+BEGIN_SRC html :tangle templates/main.html :noweb yes +** Icons + + #+begin_src css :tangle style.css +.icon svg { + fill: var(--doc); + display: inline; + width: 1em; + height: .9em; + vertical-align: text-top; +} + +.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 + +** Minify CSS + + #+begin_src shell :shebang #!/bin/bash :tangle scripts/css.sh +minify="$(npm bin)/minify" +normalize="$(npm root)/normalize.css/normalize.css" +style="style.css" + +# minify add a newline character at the end of its input +# we remove it using `head' +echo " +@charset \"UTF-8\"; +$(cat ${normalize}) +$(cat ${style}) +" | ${minify} --css | head -c -1 > style.min.css + #+end_src + + #+begin_src makefile :tangle theme.mk +style.min.css : style.css + @cleopatra echo "Minifying" "CSS" + @scripts/css.sh + +ARTIFACTS += style.min.css + +theme-build : style.min.css + #+end_src + +* HTML Templates + + It would be best if we had a preprocessing step to inject the + minified style, rather than using ~soupault~ to do the work once per + page. + + #+begin_src html :tangle templates/main.html :noweb yes <html lang="en"> <head> - <<meta-tags>> - <title><!-- page title will be inserted here --></title> - <<syncloading_html>> - <<asyncloading_html>> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <style></style> + <link href="https://soap.coffee/+vendors/katex.0.11.1+swap/katex.css" rel="stylesheet" media="none" onload="if(media!='all')media='all'"> + <title></title> </head> - <<body>> + <body> + <aside> + <nav> + <ul> + <li> + <a href="/">Technical Posts</a> + </li> + <li> + <a href="/opinions">Opinions</a> + </li> + <li> + <a href="/news">News</a> + </li> + </ul> + </nav> + </aside> + <main> + </main> + </body> </html> -#+END_SRC - -** ~<head>~ - -#+NAME: meta-tags -#+BEGIN_SRC html :noweb no-export -<meta charset="utf-8"> -<meta name="viewport" - content="width=device-width, initial-scale=1.0"> -#+END_SRC - -*** Assets Loading - -#+NAME: syncloading_html -#+BEGIN_SRC html -<link href="https://soap.coffee/+vendors/normalize.css.8.0.1/normalize.css" - rel="stylesheet"> -<link rel="stylesheet" href="/style/main.css"> -<link rel="icon" type="image/ico" href="/img/merida.webp"> -#+END_SRC - -#+NAME: asyncloading_js -#+BEGIN_SRC js -let noscript = document.getElementById('asyncloading'); -noscript.insertAdjacentHTML('beforebegin', noscript.innerText); -noscript.parentNode.removeChild(noscript); -#+END_SRC - -#+NAME: asyncloading_html -#+BEGIN_SRC html -<noscript id="asyncloading"> - <link href="https://soap.coffee/+vendors/fira-code.2+swap/font.css" - rel="stylesheet"> - <link href="https://soap.coffee/+vendors/katex.0.11.1+swap/katex.css" - rel="stylesheet"> - <link href="https://soap.coffee/+vendors/fork-awesome.1.1.7+swap/css/fork-awesome.min.css" - rel="stylesheet"> -</noscript> -#+END_SRC - -** ~body~ - -#+NAME: body -#+BEGIN_SRC html :noweb no-export -<body> - <header> - <a href="/">Technical Articles</a> / - <a href="/opinions">Opinions</a> / - <a href="/news">News</a> / - <a href="/projects">Projects</a> - </header> - <main> - <!-- page content will be inserted here --> - </main> - <footer> - <img src="https://soap.coffee/~lthms/img/merida.webp" - alt="Merida from the movie Ralph 2.0 from Disney is the - avatar I use most of the time on the Internet" - title="lthms’ avatar" /> - - <p> - Hi, I’m <strong>lthms</strong>, and this is my humble corner of the - Internet. You are very welcome here! If you are interested in - <em>functional programming</em>, <em>formal verification</em>, or <em>free - software projects in the making</em>, you may even enjoy your stay! - </p> - - <p> - This website has been generated using a collection of <a - href="/posts/Thanks.html">awesome free software projects</a>. You can have - a look at <a href="https://code.soap.coffee/writing/lthms.git/">the source - of this website</a> or <a href="/cleopatra.html">how it is being - generated</a>, and if you run into an error, feel free to <a - href="mailto:lthms@soap.coffee">shoot me a friendly email</a>. - </p> - </footer> - <script> - <<asyncloading_js>> - </script> -</body> -#+END_SRC - -* Main SASS File - -First, we introduce a set of variables. - -#+BEGIN_SRC sass :tangle site/style/main.sass -$content-width : 30rem -$large-side-margin : 7rem -$small-side-margin : 2rem -$monospace-color : #FF006D -#+END_SRC - -Then, we introduce a key CSS variable whose definition will change according to -the current width of the page (something we cannot achieve with SASS variables, -whose behavior is purely static). - -#+BEGIN_SRC sass :tangle site/style/main.sass -\:root - @media screen and (min-width: calc(#{$content-width} + 2 * #{$large-side-margin})) - --side-margin : #{$large-side-margin} - @media screen and (max-width: calc(#{$content-width} + 2 * #{$large-side-margin})) - --side-margin : #{$small-side-margin} -#+END_SRC - -Then, we style! - -#+BEGIN_SRC sass :tangle site/style/main.sass -* - box-sizing : border-box - -html - width : 100% - height : 100% - font-size : 100% - font-weight : normal - color : #222 - -a - color : black - -a:visited - color : #222 - -body - font-family : serif - margin : 0 var(--side-margin) 0 calc(var(--side-margin) - 2rem) !important - padding : 2rem - @media screen and (min-width: calc(#{$content-width} + 2 * #{$large-side-margin})) - border-left : 1px solid #ccc - -main p, -main h1, -main h2, -main h3, -main h4, -main h5, -main h6, -main ul, -main dl, -main ol, -header, -footer - max-width : $content-width - line-height : 140% - -main h1, -main h2, -main h3, -main h4, -main h5, -main h6 - font-weight : bold - color : #0c0016 - -header a, -footer p - font-size : 90% - -main - padding-top : 4rem - padding-bottom : 4rem - - dl dd - margin-left : 0 - - dl dt - font-weight : bold - - dl dt:not(:first-child) - padding-top : .5rem - - details - font-size : 90% - filter : opacity(0.8) - -footer img - border-radius : 100% - max-width : 7rem - float : right - margin-left : 1rem - margin-bottom : 1rem - -pre - padding-left : 1.5rem - padding-right : 1.5rem - overflow-x : auto - -code, -tt, -pre - font-family : 'Fira Code', monospace - font-size : 80% - line-height : 140% - color : $monospace-color - -#gallery - display : flex - flex-wrap : wrap - align-content : flex-start - - img - max-width : 20rem - -@import plugins -@import org -@import coq -#+END_SRC - -* Build Instructions - -The build instruction are pretty straightforward. We start by how to compile the -main CSS file. - -#+BEGIN_SRC makefile :tangle theme.mk -SASS := $(wildcard site/style/*.sass) -MAIN_SASS := site/style/main.sass -CSS := $(MAIN_SASS:.sass=.css) - -${CSS} : ${SASS} - @cleopatra echo Compiling "${CSS}" - @sassc --style=compressed --sass ${MAIN_SASS} ${CSS} -#+END_SRC - -Since the HTML template does not need any particular processing, the -=theme-build= phase is only responsible for generating the main CSS file. The -[[./soupault.org][=soupault= build phase]] needs to start after the CSS file is -compiled (since it copies all relevant files to the ~build/~ directory), so we -explicit this dependency. - -#+BEGIN_SRC makefile :tangle theme.mk -theme-build : ${CSS} -soupault-build : theme-build -#+END_SRC - -Therefore, at the end of this generation process only one file has been -generated. - -#+BEGIN_SRC makefile :tangle theme.mk -ARTIFACTS += ${CSS} -#+END_SRC + #+end_src |