* one.el :PROPERTIES: :ONE: one-default-with-sidebar :CUSTOM_ID: / :END: ** Static Site Generator for Emacs Lisp programmers [[youtube:GGP2mxZn4mY]] Have you ever wanted to write a blog: - contained in a unique org file, - rendered with only one Emacs command, - that can be modified by writing Emacs Lisp code (and CSS too), - with "html templates" that are plain Emacs Lisp data, - with no config file, - and no dependencies on external static site generators? If so, you might be interested in ~one.el~ a simple *Static Site Generator* for *Emacs Lisp* programmers and *org-mode* users. To get started right away check [[#/docs/install-one-el/][Install one.el]] and [[#/docs/getting-started/][Getting started]] pages. You can find the code here: https://github.com/tonyaldon/one.el. Athough ~one.el~ uses org-mode not all the org elements are useful to build technical blog sites (see [[#/#why][Why one.el?]]). So only a few org elements have an transcoder function implemented in [[#/docs/one-ox/][one-ox]], the org backend used by ~one.el~ to build the default website (see [[#/docs/one-default-render-function/][one-default render function]]). Please check [[#/docs/one-ox/#org-elements-not-supported][Org elements not supported]] before relying on ~one.el~. In ~one.el~, the following org document defines a website with 3 pages that we build by calling ~one-build~ command while we are visiting it: #+BEGIN_SRC org ,* My website :PROPERTIES: :ONE: one-default-home :CUSTOM_ID: / :END: Welcome to my website! ,* Blog post 1 :PROPERTIES: :ONE: one-default :CUSTOM_ID: /blog/page-1/ :END: My first blog post! ,* Blog post 2 :PROPERTIES: :ONE: one-default :CUSTOM_ID: /blog/page-2/ :END: My second blog post! #+END_SRC Note that if we want to use the default css style sheet we can add it by calling ~one-default-add-css-file~ before building the website. The path ~/~ in the first ~CUSTOM_ID~ org property tells ~one.el~ that the page "My website" is the home page. That page is rendered using ~one-default-home~ render function, value of ~ONE~ org property of the same headline. The path ~/blog/page-1/~ in the second ~CUSTOM_ID~ org property tells ~one.el~ that we want to render "Blog post 1" page in such a way that when we serve our website locally at ~http://localhost:3000~ for instance, that page is served at ~http://localhost:3000/blog/page-1/~. How that page is rendered is determined by the value of ~ONE~ org property of the same headline which is ~one-default~, a render function. The same goes for the last page "Blog post 2". As you might have noticed, a ~one.el~ website is an org file where the pages are the headlines of level 1 with the org properties ~ONE~ and ~CUSTOM_ID~ set. Nothing more! ~ONE~ is the only org property added by ~one.el~. Its value, an Emacs Lisp function which returns an HTML string, for a given page determines how ~one.el~ renders that page. Paths of pages are set using ~CUSTOM_ID~ org property. With that said, if you want to try it you can check [[#/docs/install-one-el/][Install one.el]] and [[#/docs/getting-started/][Getting started]] pages. ** Why one.el? :PROPERTIES: :CUSTOM_ID: /#why :END: I wrote ~one.el~ because I didn't find an existing static site generator with the following requirements: - I'm not looking for a solution for every type of websites, only for technical blog sites which are basically chunks of code surrounded by text, - I want something simple that I understand and that I can modify only by writting some Emacs Lisp, - I want websites to be written to a single org file, - I want something with no dependencies other than emacs packages that are not bridges to feed other static site frameworks, - I want something with no configuration options, if you want to modify something you write Emacs Lisp code and - Finally, I want an Emacs solution for an Emacs user. Following those requirements led me to ~one.el~, an opiniated static site generator for Emacs Lisp programmers and Org mode users that works well if you want to build websites like - [[https://minibuffer.tonyaldon.com][minibuffer]] ([[https://github.com/tonyaldon/minibuffer.tonyaldon.com][source]]): learn Emacs Lisp one sexp at a time, - [[https://posts.tonyaldon.com][Elisp posts]]: some articles about Emacs Lisp, - [[https://jack.tonyaldon.com][jack]]: HTML generator library for Emacs Lisp, - [[https://one.tonyaldon.com][one.el]]: documentation of one.el package, - [[https://lnroom.live][LNROOM]]: learn how to hack on Core Lightning and - https://tonyaldon.com. all built with ~one.el~. * Install one.el :PROPERTIES: :ONE: one-default-doc :CUSTOM_ID: /docs/install-one-el/ :END: ** Manually ~one.el~ depends on [[https://jack.tonyaldon.com/][jack]] and [[https://github.com/hniksic/emacs-htmlize][htmlize]] packages that are available on [[https://melpa.org/][Melpa]]. Once you have them installed you can add ~one.el~ to your ~load-path~ and require it like this: #+BEGIN_SRC emacs-lisp (add-to-list 'load-path "/path/to/one.el/") (require 'one) #+END_SRC ** With package-install ~one.el~ is also available on [[https://melpa.org/][Melpa]] so you can install it like this: #+BEGIN_SRC text M-x package-install one #+END_SRC ** With straight.el If you're using [[https://github.com/radian-software/straight.el][straight.el]], to install ~one.el~ you just have to add this sexp to your init file: #+BEGIN_SRC emacs-lisp (straight-use-package '(one :type git :host github :repo "tonyaldon/one.el" :build (:not compile))) #+END_SRC Note that ~:build (:not compile)~ is important. It tells ~straight.el~ not to byte compile ~one.el~. Something happened in ~straight.el~ between commits ~3eca39d~ and ~b3760f5~ which broke byte compilation of ~one.el~ if done by ~straight.el~. ** Let's go Now you can create a new website by calling ~one-default-new-project~ (preferably in an empty directory) and you can build it by calling ~one-build~ command. If this is the first time you try ~one.el~ reading [[#/docs/getting-started/][Getting started]] page might be helpful. * Getting started :PROPERTIES: :ONE: one-default-doc :CUSTOM_ID: /docs/getting-started/ :END: ** Start a new project By calling ~one-default-new-project~ command (preferably in an empty directory) we produce a new ~one.el~ project with the following structure: #+BEGIN_SRC text . ├── assets │ └── one.css └── one.org #+END_SRC Once done we can build the website under the directory ~./public/~ by calling ~one-build~ command while we are in the file ~one.org~. Our project's structure is now: #+BEGIN_SRC text . ├── assets │ └── one.css ├── one.org └── public ├── blog │ ├── default │ │ └── index.html │ ├── default-home-list-pages │ │ └── index.html │ ├── one-default-doc │ │ └── index.html │ ├── one-default-with-sidebar │ │ └── index.html │ └── one-default-with-toc │ └── index.html ├── index.html └── one.css #+END_SRC ** Modify the content with live reloading To get our website up and running, we serve the files in ~./public/~ subdirectory using [[https://browsersync.io][brower-sync]] (any webserver serving files is OK). Once we have it installed, to start a webserver with live reloading, we run the following commands (in a terminal): #+BEGIN_SRC text $ cd public $ browser-sync start -s -w --files "*" #+END_SRC Assuming the port ~3000~ isn't used we have our website served at ~http://localhost:3000~. Now we can modify the content of ~one.org~ file and see the changes reflected in the browser after we rebuild/re-render the whole website or part of it using the following commands ~one-build~, ~one-render-pages~ and ~one-render-page-at-point~ or the asynchronous version of those commands ~one-build-async~, ~one-render-pages-async~ and ~one-render-page-at-point-async~. ** CSS style sheet When we call ~one-build~ (or ~one-build-async~) command the pages of the website are rendered in the directory ~./public/~ and the files in ~./assets/~ directory are copied into ~./public/~ subdirectory. When we build a ~one.el~ website with the default render functions and the default CSS style sheet (this is the case if we used ~one-default-new-project~ as we did above) the style sheet that applies is ~./public/one.css~ file which is a copy of ~./assets/one.css~ file. So in that case, to modify the website's layout we just have to modify the file ~./assets/one.css~ and copy it in ~./public/~ directory either with ~one-build~, ~one-build-async~ or ~one-copy-assets-to-public~. *** Modify the CSS style sheet with live reloading To get the file ~./assets/one.css~ copied into ~./public/~ directory each time we modify it we can use [[https://eradman.com/entrproject/][entr]] utility like this (being at the root of our project): #+BEGIN_SRC text $ ls assets/one.css | entr -s 'cp assets/one.css public/' #+END_SRC Combined with ~browser-sync~ live reloading I think we get a decent programmer experience. *** Source blocks When we use the default render functions and the default CSS style sheet, the org content is exported into HTML strings using ~one-ox~ org export backend. Consequently, ~src-block~ elements are highlighted using [[https://github.com/hniksic/emacs-htmlize][htmlize]]. See [[#/docs/one-ox-src-block/][one-ox | src-block]] for more information. * How does one.el work? :PROPERTIES: :ONE: one-default-doc :CUSTOM_ID: /docs/how-does-one-el-work/ :END: In an org file containing all the pages of our website we can build the website under ~./public/~ subdirectory by calling either ~one-build~ or ~one-render-pages~ commands. The only difference between those two commands is that before producing the HTML pages calling ~one-render-pages~, ~one-build~ command cleans the subdirectory ~./public/~ and copies the content of ~./assets/~ subdirectory into ~./public/~ subdirectory. So let's focus on ~one-render-pages~ command. For each page of our website, the function ~one-render-pages~ uses the render function set in ~ONE~ org property of the page to produce the HTML string representing the page and stores it in an ~index.html~ file whom path is determined by ~CUSTOM_ID~ org property of the page. Render functions are at the heart of ~one.el~ mechanism. They determined how pages are rendered. Specifically, render functions are regular Elisp functions that takes 3 arguments - ~page-tree~: corresponding to the parsed tree of the org entry defining the page, - ~pages~: the list of pages, - ~global~: a plist of global informations that are computed once in ~one-render-pages~ (see ~one-add-to-global~) before rendering the pages and return HTML strings. For instance, the following ~hello-world~ function #+BEGIN_SRC emacs-lisp (defun hello-world (page-tree pages global) "

Hello world!

") #+END_SRC defines a valid render function. We can use it to build a website like this. In an empty directory, we create a file named ~one.org~ with the following content: #+BEGIN_SRC org ,* The home page :PROPERTIES: :ONE: hello-world :CUSTOM_ID: / :END: ,* Blog post 1 :PROPERTIES: :ONE: hello-world :CUSTOM_ID: /blog/page-1/ :END: #+END_SRC We visit that file and call ~one-build~ command. It produces the following files #+BEGIN_SRC text . ├── one.org (already there) └── public ├── blog │ └── page-1 │ └── index.html └── index.html #+END_SRC and both files ~./public/blog/page-1/index.html~ and ~./public/index.html~ have the same content: #+BEGIN_SRC html

Hello world!

#+END_SRC Therefore if we serve the website in ~./public/~ directory at ~http://localhost:3000~ we can access the two "Hello world!" pages at ~http://localhost:3000/blog/page-1/~ and ~http://localhost:3000~. That's it! This is how ~one.el~ works under the hood. ~one.el~ comes with predefined render functions, a custom CSS style sheet and a custom [[#/docs/one-ox/][org export backend]] which are used all together to build that documentation for instance. See [[#/docs/getting-started/][Getting started]] to start a new project with those defaults. See [[#/docs/one-default-render-function/][one-default render function]] to take inspiration and write your own render functions. * one-default render function :PROPERTIES: :ONE: one-default-doc :CUSTOM_ID: /docs/one-default-render-function/ :END: In [[#/docs/how-does-one-el-work/][How does one.el work?]] page we saw that render functions are at the heart of ~one.el~ mechanism. They determine how pages are rendered. We saw that #+BEGIN_SRC emacs-lisp (defun hello-world (page-tree pages global) "

Hello world!

") #+END_SRC defines a valid render function that can be used to render pages of a ~one.el~ website by setting ~ONE~ org property to ~hello-world~ like this for instance: #+BEGIN_SRC org ,* The home page :PROPERTIES: :ONE: hello-world :CUSTOM_ID: / :END: ,* Blog post 1 :PROPERTIES: :ONE: hello-world :CUSTOM_ID: /blog/page-1/ :END: #+END_SRC ~one.el~ comes with several default render functions that can be used instead of the dummy ~hello-world~ function: - ~one-default-home~: org content, - ~one-default-home-list-pages~: org content followed by the list in reverse order of the pages of the website, - ~one-default~: org content with navigation buttons at the bottom to go to the previous page, the next page or a random one, - ~one-default-with-toc~: same as ~one-default~ but with a table of content at the top of the page and - ~one-default-with-sidebar~: same as ~one-default~ but with a sidebar listing all the pages in the website, - ~one-default-doc~: same as ~one-default-with-sidebar~ but with a table of content at the top of the page. Those default render functions use [[#/docs/one-ox/][one-ox]] custom org export backend and ~one-default-css~ custom CSS style sheet. If we want to start a new project using these defaults, we can use ~one-default-new-project~ command (see [[#/docs/getting-started/][Getting started]]). If you plan to write your own render functions you may find the following sections interesting. *** The org document Let's consider the following org document in a file named ~one.org~ for instance: #+BEGIN_SRC org ,* Home :PROPERTIES: :ONE: one-default-home :CUSTOM_ID: / :END: ,* Page 1 :PROPERTIES: :ONE: one-default :CUSTOM_ID: /blog/page-1/ :END: ,** Headline foo 1 [[#/blog/page-2/][Link to Page 2]] ,** Headline foo 2 ,*** Headline bar Some content. ,*** Headline baz :PROPERTIES: :CUSTOM_ID: /blog/page-1/#baz :END: ,#+BEGIN_SRC emacs-lisp (message "foo bar baz") ,#+END_SRC ,* Page 2 :PROPERTIES: :ONE: one-default :CUSTOM_ID: /blog/page-2/ :END: [[#/blog/page-1/#baz][Link to Headline baz in Page 1]] #+END_SRC Let's generate the file ~./assets/one.css~ that contains the content of ~one-default-css~ string by calling ~one-default-add-css-file~ command. Our project structure is now: #+BEGIN_SRC text . ├── assets │ └── one.css └── one.org #+END_SRC *** Build the website Now, while vising the file ~one.org~ we call ~one-build~ which builds "Home", "Page 1" and "Page 2" pages under the directory ~./public/~ such that our project tree is now: #+BEGIN_SRC text . ├── assets │ └── one.css ├── one.org └── public ├── blog │ ├── page-1 │ │ └── index.html │ └── page-2 │ └── index.html ├── index.html └── one.css #+END_SRC *** Home The page "Home" has been generated: - in the file ~./public/index.html~ respecting the path information ~/~ in ~CUSTOM_ID~ org property and - its HTML content has been created using ~one-default-home~ render function specified in ~ONE~ org property. ~./public/index.html~ (pretty printed for the demonstration): #+BEGIN_SRC html Home
Home
#+END_SRC *** Page 1 The page "Page 1" has been generated: - in the file ~./public/blog/page-1/index.html~ respecting the path information ~/blog/page-1/~ in ~CUSTOM_ID~ org property and - its HTML content has been created using ~one-default~ render function specified in ~ONE~ org property. ~./public/blog/page-1/index.html~ (pretty printed for the demonstration): #+BEGIN_SRC html Page 1

Page 1

Headline foo 1

Link to Page 2

Headline foo 2

Headline bar

Some content.

Headline baz

(message "foo bar baz")
#+END_SRC *** Page 2 The page "Page 2" has been generated: - in the file ~./public/blog/page-2/index.html~ respecting the path information ~/blog/page-2/~ in ~CUSTOM_ID~ org property and - its HTML content has been created using ~one-default~ render function specified in ~ONE~ org property. ~./public/blog/page-2/index.html~ (pretty printed for the demonstration): #+BEGIN_SRC html Page 2 #+END_SRC *** How was "Page 1" built? When we called ~one-build~ in ~one.org~ buffer, the whole buffer was parsed with the function ~one-parse-buffer~ and a list of pages was built from that parsed tree and looked like this: #+BEGIN_SRC emacs-lisp ((:one-title "Home" :one-path "/" :one-render-page-function one-default-home :one-page-tree (headline (:raw-value "Home" ...) ...)) (:one-title "Page 1" :one-path "/blog/page-1/" :one-render-page-function one-default :one-page-tree (headline (:raw-value "Page 1" ...) ...)) (:one-title "Page 2" :one-path "/blog/page-2/" :one-render-page-function one-default :one-page-tree (headline (:raw-value "Page 2" ...) ...))) #+END_SRC Let's call ~pages~ that list of pages. Then for each ~page~ in ~pages~ the function ~one-render-page~ was called with ~page~, ~pages~ and ~global~ (see ~one-add-to-global~ variable) as arguments. Finally, in ~one-render-page~ the function ~one-default~ or ~one-default-home~ was called with the arguments ~page-tree~, ~pages~ and ~global~ to create the HTML content of each page whom path under the directory ~./public/~ was determined by the value of ~:one-path~ property in ~page~ and ~page-tree~ was the value of ~:one-page-tree~ property in ~page~. Focusing on "Page 1", the function ~one-default~ was called with the arguments ~page-tree~, ~page~ and ~global~ with ~page-tree~ being the following parsed tree of the headline defining "Page 1": #+BEGIN_SRC emacs-lisp (headline (:raw-value "Page 1" :CUSTOM_ID "/blog/page-1/" :ONE "one-default" :parent (org-data ...) :one-internal-id "one-9c81c230b6" ...) (section (...) (property-drawer ...)) (headline (:raw-value "Headline foo 1" :one-internal-id "one-4df8d962d9" ...) (section (...) (paragraph ...))) (headline (:raw-value "Headline foo 2" :one-internal-id "one-9d89da8271" ...) (headline (:raw-value "Headline bar" :one-internal-id "one-95fa001487" ...) (section (...) (paragraph (...) #("Some content. " 0 14 (:parent #4))))) (headline (:raw-value "Headline baz" :CUSTOM_ID "/blog/page-1/#baz" :one-internal-id "baz" ...) (section (...) (property-drawer ...) (src-block (:language "emacs-lisp" :value "(message \"foo bar baz\")" ...)))))) #+END_SRC In ~one-default~ the org content of "Page 1" was exported into a HTML string using ~org-export-data-with-backend~ and [[#/docs/one-ox/][one-ox]] custom org export backend. Then this HTML string was used in a data structure representing the HTML page. Finally, ~jack-html~ (see [[https://jack.tonyaldon.com/][jack]]) transformed that data structure into a HTML string which was written on the file ~./public/blog/page-1/index.html~: #+BEGIN_SRC emacs-lisp (defun one-default (page-tree pages _global) "Default render function. See `one-is-page', `one-render-pages' and `one-default-css'." (let* ((title (org-element-property :raw-value page-tree)) (path (org-element-property :CUSTOM_ID page-tree)) (content (org-export-data-with-backend (org-element-contents page-tree) 'one-ox nil)) (website-name (one-default-website-name pages)) (nav (one-default-nav path pages))) (jack-html "" `(:html (:head (:meta (@ :name "viewport" :content "width=device-width,initial-scale=1")) (:link (@ :rel "stylesheet" :type "text/css" :href "/one.css")) (:title ,title)) (:body (:div.header (:a (@ :href "/") ,website-name)) (:div.content (:div.title ,(if (not (string= path "/")) `(:div.title (:h1 ,title)) '(:div.title-empty))) ,content ,nav)))))) #+END_SRC * Miscellaneous :PROPERTIES: :ONE: one-default-doc :CUSTOM_ID: /docs/miscellaneous/ :END: ** Page at point If we need to render only the page at point, meaning the headline of level 1 with ~ONE~ and ~CUSTOM_ID~ org properties set, we can use the commands ~one-render-page-at-point~ and ~one-render-page-at-point-async~. ** onerc.el file We can use an Emacs Lisp file called ~onerc.el~ to customize our website. It must be in the same directory of the org file containing the content of our website. This file is loaded first in ~one-render-pages~ before rendering the webpages. This is a good place to set ~one-add-to-global~ and ~one-hook~ variables or to define our own render functions. ** one-add-to-global :PROPERTIES: :CUSTOM_ID: /docs/miscellaneous/#one-add-to-global :END: Render functions takes 3 arguments: - ~page-tree~: the parsed tree of the page being rendered, - ~pages~: the list of pages, - ~global~: a plist of global informations that are computed once in ~one-render-pages~ before rendering the pages using ~one-add-to-global~ variable. That means that if a render function needs extra informations, we can use ~one-add-to-global~ variable to pass those informations to the render function. Specifically, elements in ~one-add-to-global~ list are plist with the following properties: - ~:one-global-property~: a keyword that is used as proprety in the ~global~ argument passed to the render functions, - ~:one-global-function~: a function that takes two arguments ~pages~ (list of pages, see ~one-list-pages~) and ~tree~ (see ~one-parse-buffer~). That function is called once in ~one-render-pages~ and its result is used as the value of the property ~:one-global-property~ in the ~global~ argument passed to the render functions. For instance, if ~one-add-to-global~ is set to #+BEGIN_SRC emacs-lisp ((:one-global-property :one-tree :one-global-function (lambda (pages tree) tree))) #+END_SRC then ~global~ local variable will be set to #+BEGIN_SRC emacs-lisp ((:one-tree tree)) #+END_SRC where ~tree~ is the value returned by ~one-parse-buffer~ function. ** one-hook Each function in ~one-hook~ is called once in ~one-render-pages~. Those functions take three arguments: - ~pages~: list of pages (see ~one-list-pages~), - ~tree~: see ~one-parse-buffer~, - ~global~: see [[#/docs/miscellaneous/#one-add-to-global][one-add-to-global]]. As those functions take ~global~ argument they are called after that argument has been let binded using ~one-add-to-global~. *** feed.xml example This hook is used to build ~feed.xml~ file of [[https://minibuffer.tonyaldon.com][minibuffer.tonyaldon.com]] website. You can check ~onerc.el~ file of [[https://github.com/tonyaldon/minibuffer.tonyaldon.com][tonyaldon/minibuffer.tonyaldon.com]] repository to see how it is done. *** robot.txt and sitemap.txt If we want to add a ~sitemap.txt~ file to our website we can do so using ~one-hook~. **** robot.txt First we need to indicate in a ~robots.txt~ where our ~sitemap.txt~ is located. Assuming our website is ~https://example.com~ and our ~sitemap.txt~ file is at the root of it, we can add the following ~robots.txt~ file in the ~assets~ directory (~./assets/robots.txt~): #+BEGIN_SRC text User-Agent: * Allow: / Sitemap: https://domain.com/sitemap.txt #+END_SRC **** sitemap.txt Now in ~onerc.el~ file: 1) we set our domain with protocol in the variable ~domain~, 2) then we define ~make-sitemap~ function which will create the file ~sitemap.txt~ in the ~public~ directory (~./public/sitemap.txt~) each time be build our website, 3) Finally, to tell ~one.el~ to actually create ~sitemap.txt~ file using ~make-sitemap~ function each time be build our website, we add it to ~one-hook~: #+BEGIN_SRC emacs-lisp (defvar domain "https://example.com" "Domain with protocol to be used to produce sitemap file. See `make-sitemap'.") (defun make-sitemap (pages tree global) "Produce file ./public/sitemap.txt Global variable `domain' is used as domain with protocol. This function is meant to be added to `one-hook'." (with-temp-file "./public/sitemap.txt" (insert (mapconcat 'identity (mapcar (lambda (page) (let* ((path (plist-get page :one-path)) (link (concat domain path))) link)) pages) "\n")))) (add-hook 'one-hook 'make-sitemap) #+END_SRC Thanks [[https://github.com/tanrax][@tanrax]] for the code snippet (see [[https://github.com/tonyaldon/one.el/issues/6][issue #6]]). ** Async commands The function ~one-render-pages-async~ and ~one-build-async~ spawn an ~emacs~ subprocess in order to build html pages asynchronously. The arguments passed to ~emacs~ depends on ~one-emacs-cmd-line-args-async~ value. By default, when ~one-emacs-cmd-line-args-async~ is ~nil~, we run ~emacs~ in "batch mode", we load the user's initialization file and we evaluate a specific sexp that builds html pages. Specifically, we pass the following ~command~ (~emacs~ file name followed by command line arguments) to ~make-process~ function like this: #+BEGIN_SRC emacs-lisp (let* ((emacs (file-truename (expand-file-name invocation-name invocation-directory))) (command `(,emacs "--batch" "-l" ,user-init-file "--eval" ,sexp)) (sexp ...)) (make-process :name ... :buffer ... :command command)) #+END_SRC If ~one-emacs-cmd-line-args-async~ is non-nil, we no longer load the user's initialization file and replace ~"-l" ,user-init-file~ in ~command~ above by the elements of ~one-emacs-cmd-line-args-async~. For instance, if ~one-emacs-cmd-line-args-async~ is equal to #+BEGIN_SRC emacs-lisp '("-l" "/path/to/some-elisp-file.el") #+END_SRC then ~command~ becomes #+BEGIN_SRC emacs-lisp (let* (... (command `(,emacs "--batch" "-l" "/path/to/some-elisp-file.el" "--eval" ,sexp)) ...) ...) #+END_SRC ** Extend one-ox org backend :PROPERTIES: :CUSTOM_ID: /docs/miscellaneous/#extend-one-ox-org-backend :END: When we use the default render functions, the org content of the webpages is exported using [[#/docs/one-ox/][one-ox]] org backend like this #+BEGIN_SRC emacs-lisp (org-export-data-with-backend (org-element-contents page-tree) 'one-ox nil) #+END_SRC where ~page-tree~ is the parsed tree of the headline containing the page being rendered (see [[#/docs/one-default-render-function/][one-default render function]]). While ~one-ox~ exports enough org elements for my use cases (see [[#/#why][Why one.el?]]) this might not be the case for you. I think this is not a big problem because we can extend ~one-ox~ (precisely we can derive a new org backend from ~one-ox~ org backend) with other transcoder functions for the org elements that miss transcoder functions. Let's see how we can do that with an example. *** Extend one-ox with horizontal-rule org elements Lines consisting of only dashes (at least 5) are parsed by the org parser as ~horizontal-rule~ org elements. ~one-ox~ doesn't provide a transcoder function for ~horizontal-rule~ so we can't use it directly if we want to have them exported as ~
~ tags in our website. In that section we see how to derived an org backend ~one-ox-with-hr~ from ~one-ox~ org backend that exports ~horizontal-rule~ org elements with ~
~ tags. To do that we define a transcoder function ~my-horizontal-rule~ which takes 3 arguments (not used) and return the string ~"
"~: #+BEGIN_SRC emacs-lisp (defun my-horizontal-rule (_ _ _) "
") #+END_SRC Then we use that function in the ~:translate-alist~ alist in the body of the function ~org-export-define-derived-backend~ to define ~one-ox-with-hr~ org backend: #+BEGIN_SRC emacs-lisp (org-export-define-derived-backend 'one-ox-with-hr 'one-ox :translate-alist '((horizontal-rule . my-horizontal-rule))) #+END_SRC Then we can export the org content of the webpages (including the ~horizontal-rule~) using ~one-ox-with-hr~ org backend like this #+BEGIN_SRC emacs-lisp (org-export-data-with-backend (org-element-contents page-tree) 'one-ox-with-hr nil) #+END_SRC where ~page-tree~ is the parsed tree of the headline containing the page being rendered. Now that we saw how to derive ~one-ox-with-hr~ org backend and use it, let's build a website with only a home page with two ~horizontal-rule~. In an empty directory let's add the following files: - ~one.org~: #+BEGIN_SRC org ,* Home page :PROPERTIES: :ONE: my-render-function :CUSTOM_ID: / :END: foo ----- bar ----- baz #+END_SRC - ~onerc.el~: #+BEGIN_SRC emacs-lisp (defun my-horizontal-rule (_ _ _) "
") (org-export-define-derived-backend 'one-ox-with-hr 'one :translate-alist '((horizontal-rule . my-horizontal-rule))) (defun my-render-function (page-tree pages _global) "" (let* ((title (org-element-property :raw-value page-tree)) (content (org-export-data-with-backend (org-element-contents page-tree) 'one-ox-with-hr nil))) (jack-html "" `(:html (:head (:title ,title)) (:body (:h1 ,title) ,content))))) #+END_SRC Now while visiting ~one.org~ file we call ~one-build~ to build our website with ~
~ tags. * one-ox :PROPERTIES: :ONE: one-default-doc :CUSTOM_ID: /docs/one-ox/ :END: ** Org export backend used by the default render functions ~one.el~ (specifically the default render functions) uses its own org export backend called ~one-ox~ to export the org content of the pages into HTML strings. For instance, the render function ~one-default~ takes as first argument ~page-tree~ which is the current page being rendered (~page-tree~ is the org parsed data structure representing the page) and exports it as an HTML string using ~org-export-data-with-backend~ function and ~one-ox~ export backend and uses it to render the HTML page: #+BEGIN_SRC emacs-lisp (defun one-default (page-tree pages _global) "..." (let* (... (content (org-export-data-with-backend (org-element-contents page-tree) 'one-ox nil)) ...) (jack-html "" `(:html (:head ...) (:body ... (:div.content ... ,content ,nav)))))) #+END_SRC This org backend is taylor for ~one.el~ usage. So it doesn't try to export all the org elements unlike ~html~ backend and when the org elements are exported they differ from what we can expect from ~html~ backend. For instance ~headline~ elements don't take into account markups neither links. Another example are the ~link~ elements. They don't support org fuzzy links and links to local files that are not in the subdirectories ~./public/~ or ~./assets/~ raise errors. You can read how the supported org elements are exported by ~one-ox~ org backend in the following page: - [[#/docs/one-ox-headline/][one-ox | headline]], - [[#/docs/one-ox-src-block/][one-ox | src-block]], - [[#/docs/one-ox-quote-block/][one-ox | quote-block]], - [[#/docs/one-ox-fixed-width-and-example-block/][one-ox | fixed-width and example-block]], - [[#/docs/one-ox-links/][one-ox | links]] and - [[#/docs/one-ox-plain-list/][one-ox | plain-list]]. ** Org elements not supported :PROPERTIES: :CUSTOM_ID: /docs/one-ox/#org-elements-not-supported :END: The org elements that are not supported are the following: ~center-block~, ~clock~, ~drawer~, ~dynamic-block~, ~entity~, ~export-block~, ~export-snippet~, ~footnote-reference~, ~horizontal-rule~, ~inline-src-block~, ~inlinetask~, ~keyword~, ~latex-environment~, ~latex-fragment~, ~line-break~, ~node-property~, ~planning~, ~property-drawer~, ~radio-target~, ~special-block~, ~statistics-cookie~, ~target~, ~timestamp~, ~verse-block~. Note that "not supported" means they are not rendered by default by ~one.el~ but we can still use them or even extend ~one-ox~ org export backend to take some of them into account. Why doesn't ~one.el~ support all org elements? 1. I don't need those org elements to write my technical blogs: - I don't do math. No support for Latex, - etc. 2. ~one-ox~ org backend is used only by the default render functions, so if you need more org elements you can either use another org backend or extend ~one-ox~ org backend and use this other org backend in your own render functions (See [[#/docs/miscellaneous/#extend-one-ox-org-backend][Extend one-ox org backend]]). * one-ox | headline :PROPERTIES: :ONE: one-default-doc :CUSTOM_ID: /docs/one-ox-headline/ :END: Note that markups and links are not exported if used in headlines, only the raw value string. So don't use them in headlines. * one-ox | src-block :PROPERTIES: :ONE: one-default-doc :CUSTOM_ID: /docs/one-ox-src-block/ :END: ** Code highlighting with htmlize *** Description ~one-ox~ highlights code via the function ~one-ox-htmlize~ that uses [[https://github.com/hniksic/emacs-htmlize][htmlize]] to do the work. For a given piece of code ~X~ in a certain language ~Y~, ~X~ will be highlighted as it would be in the emacs mode ~Z~ used to edit ~Y~ code. For instance, ~clojure-mode~ is used to highlight Clojure code and ~sh-mode~ is used to highlight Bash code. Attributes of a face (like ~background-color~ or ~foreground-color~) are not taken directly. A generated name for the face is produced and used as the CSS class for the parts of the code ~X~ that are highlighted with that face. For instance, in ~sh-mode~, the word ~echo~ is highlighted with the face ~font-lock-builtin-face~. So, the word ~echo~ in a piece of Shell (or Bash) code will be transformed into: #+BEGIN_SRC html echo #+END_SRC The whole piece of code ~X~, once the previously described operations have been done, is wrapped: 1) for a normal block with the component: #+BEGIN_SRC html
...
#+END_SRC 2) for a result block with the component: #+BEGIN_SRC html
...
#+END_SRC See section [[#/docs/one-ox-src-block/#org-keywords-results-and-attr_one_results][org keyword RESULTS]]. *** Example with Bash code For instance, the following org src-block, containing some ~bash~ code: #+BEGIN_SRC org ,#+BEGIN_SRC bash echo "list file's extensions in current dir:" for f in `ls`; do echo ${f##*.} done ,#+END_SRC #+END_SRC is exported as follow: #+BEGIN_SRC html
echo "list file's extensions in current dir:"
for f in `ls`; do
    echo ${f##*.}
done
#+END_SRC and rendered like this: #+BEGIN_SRC bash echo "list file's extensions in current dir:" for f in `ls`; do echo ${f##*.} done #+END_SRC Note that ~one-ox-htmlize~ has produced and used the following CSS classes (listed with their corresponding emacs faces): #+BEGIN_SRC text # from font-lock one-hl-builtin --> font-lock-builtin-face one-hl-keyword --> font-lock-keyword-face one-hl-string --> font-lock-string-face one-hl-variable-name --> font-lock-variable-name-face # specific to sh-mode one-hl-sh-quoted-exec --> sh-quoted-exec #+END_SRC You might have notice the pattern used for ~font-lock~ faces and the one used for mode specific faces. ~one.el~ provides a default style sheet (~one-default-css~) that has the CSS classes defined for all the ~font-lock~ faces (faces starting by ~font-lock-~) but not the specific faces used by each prog mode. You can add the CSS classes specific to the prog modes you use as you go and need them. ** Org keyword RESULTS :PROPERTIES: :CUSTOM_ID: /docs/one-ox-src-block/#org-keywords-results-and-attr_one_results :END: Result blocks are preceded by a line starting with ~#+RESULTS:~. Blocks that are not result blocks are normal blocks. When exported, normal blocks and result blocks differ only by their CSS classes: - ~one-hl one-hl-block~ for normal blocks, - ~one-hl one-hl-results~ for result blocks. This way result blocks can be rendered with a different style than normal blocks as we can see in the following example. *** Example using org keyword 'RESULTS' The following org snippet: #+BEGIN_SRC org ,#+BEGIN_SRC bash :results output ls ,#+END_SRC ,#+RESULTS: : assets : docs.org : public #+END_SRC is exported by ~one-ox~ as follow: #+BEGIN_SRC html
ls
assets
docs.org
public
#+END_SRC and is rendered by ~one-ox~ with the first block (normal block) having a different style from second block (result block): #+BEGIN_SRC bash :results output ls #+END_SRC #+RESULTS: : assets : docs.org : public ** Code blocks inside list Lists can contain source blocks as we can see in the following org snippet #+BEGIN_SRC org 1. item 1 ,#+BEGIN_SRC emacs-lisp (message "src-block in item 1") ,#+END_SRC 2. item 2 3. item 3 #+END_SRC which is exported by ~one~ as follow #+BEGIN_SRC html
  1. item 1

    (message "src-block in item 1")
  2. item 2

  3. item 3

#+END_SRC and is rendered by ~one-ox~ like this: 1. item 1 #+BEGIN_SRC emacs-lisp (message "src-block in item 1") #+END_SRC 2. item 2 3. item 3 * one-ox | quote-block :PROPERTIES: :ONE: one-default-doc :CUSTOM_ID: /docs/one-ox-quote-block/ :END: Blocks defined with ~#+BEGIN_QUOTE ... #+END_QUOTE~ pattern are quote-block. They are exported by ~one-ox~ in a ~
...
~ component with the CSS class ~one-blockquote~. The following org snippet: #+BEGIN_SRC org ,#+BEGIN_QUOTE A quitter never wins and a winner never quits. —Napoleon Hill ,#+END_QUOTE #+END_SRC defines a quote and is exported by ~one-ox~ as follow #+BEGIN_SRC html

A quitter never wins and a winner never quits. —Napoleon Hill

#+END_SRC and looks like this #+BEGIN_QUOTE A quitter never wins and a winner never quits. —Napoleon Hill #+END_QUOTE * one-ox | fixed-width and example-block :PROPERTIES: :ONE: one-default-doc :CUSTOM_ID: /docs/one-ox-fixed-width-and-example-block/ :END: ** Description A line starting with a colon ~:~ followed by a space defines a ~fixed-width~ element. A ~fixed-width~ element can span several lines. Blocks defined with ~#+BEGIN_EXAMPLE ... #+END_EXAMPLE~ pattern are ~example-block~ elements. Both ~fixed-width~ and ~example-block~ blocks are treated as [[#/docs/one-ox-src-block/][src-block]] in ~text-mode~. So: 1. they are highlighted as ~text-mode~ would do, 2. they are exported in ~
...
~ components (indentation and newlines are respected) and 3. the CSS classes used depend on the block's type: - normal blocks use ~one-hl one-hl-block~ CSS classes and - result blocks use ~one-hl one-hl-results~ CSS classes (see [[#/docs/one-ox-src-block/#org-keywords-results-and-attr_one_results][org keyword RESULTS]]). ** Example The following org snippet #+BEGIN_SRC org Here is a ~fixed-width~ element (one line): : I'm a fixed-width element ~fixed-width~ elements can also be used within lists: - item 1 : fixed-width element - item 2 ,#+BEGIN_SRC bash :results output printf 'multiline fixed-width element\nthat is also a result block,\nso has a different style.' ,#+END_SRC ,#+RESULTS: : multiline fixed-width element : that is also a result block, : so has a different style. Although I don't often use ~example-block~ elements, here is one: ,#+BEGIN_EXAMPLE This is an example! ,#+END_EXAMPLE #+END_SRC is exported by ~one~ as follow #+BEGIN_SRC html

Here is a fixed-width element (one line):

I'm a fixed-width element

fixed-width elements can also be used within lists:

Although I don't often use example-block elements, here is one:

This    is
        an    example!
#+END_SRC and looks like this: Here is a ~fixed-width~ element (one line): : I'm a fixed-width element ~fixed-width~ elements can also be used within lists: - item 1 : fixed-width element - item 2 #+BEGIN_SRC bash :results output printf 'multiline fixed-width element\nthat is also a result block,\nso has a different style.' #+END_SRC #+RESULTS: : multiline fixed-width element : that is also a result block, : so has a different style. Although I don't often use ~example-block~ elements, here is one: #+BEGIN_EXAMPLE This is an example! #+END_EXAMPLE * one-ox | links :PROPERTIES: :ONE: one-default-doc :CUSTOM_ID: /docs/one-ox-links/ :END: ** http, https, mailto links Web links (starting by ~http~ or ~https~) and links to message composition (starting by ~mailto~) are exported as we expect. For instance the following link #+BEGIN_SRC org http://tonyaldon.com #+END_SRC is exported as follow #+BEGIN_SRC html http://tonyaldon.com #+END_SRC and rendered like this: http://tonyaldon.com. This following link with a description #+BEGIN_SRC org [[https://tonyaldon.com][Tony Aldon (https)]] #+END_SRC is exported as follow #+BEGIN_SRC html Tony Aldon (https) #+END_SRC and rendered like this: [[https://tonyaldon.com][Tony Aldon (https)]]. This ~mailto~ link #+BEGIN_SRC org [[mailto:tony@tonyaldon.com][send me an email]] #+END_SRC is exported as follow #+BEGIN_SRC html send me an email #+END_SRC and rendered like this: [[mailto:tony@tonyaldon.com][send me an email]]. ** Custom ID links In ~one.el~, ~CUSTOM_ID~ org property is used to defined the path of pages or the path to specific heading in pages. Considering the following org document #+BEGIN_SRC org ,* Home Page :PROPERTIES: :ONE: one-default-home :CUSTOM_ID: / :END: - [[#/blog/page-1/]] - [[#/blog/page-1/#headline-1]] ,* Page 1 :PROPERTIES: :ONE: one-default :CUSTOM_ID: /blog/page-1/ :END: ,** headline 1 in Page 1 :PROPERTIES: :CUSTOM_ID: /blog/page-1/#headline-1 :END: #+END_SRC the link ~[[#/blog/page-1/]]~ in "Home Page" targets "Page 1" page and the link ~[[#/blog/page-1/#headline-1]]~ in "Home Page" targets the heading "headline 1 in page Page 1" in the "Page 1" page. Those paths define valid web urls starting at the root of the website if we respect the following rules for ~CUSTOM_ID~ values: 1. we use only url-encoded characters, 2. we start them with a ~/~ and end them with ~/~ excepted for the home page which is a single ~/~, 3. we use ~#~ character to start the last part of the path when we are targeting a heading tag with its ~id~ being the last part after the ~#~ character. The benefits of these "rules/conventions" are: 1. when we export ~custom-id~ links using ~one-ox~ org backend we can leave them as they are and 2. the navigation between pages inside emacs using ~custom-id~ links works out-of-the-box. *** Example of a link to a page The following link #+BEGIN_SRC org [[#/docs/one-ox-plain-list/][one-ox | plain-list]] #+END_SRC is exported to this anchor tag that links to the page ~/docs/one-ox-plain-list/~: #+BEGIN_SRC html one-ox | plain-list #+END_SRC and is rendered like this [[#/docs/one-ox-plain-list/][one-ox | plain-list]]. *** Example of a link to a heading in a page The following link #+BEGIN_SRC org [[#/docs/one-ox-plain-list/#unordered-lists][unordered lists heading in the page about plain-list]] #+END_SRC is exported to this anchor tag that links to the heading with the ~id~ set to ~unordered-lists~ on the page ~/docs/one-ox-plain-list/~: #+BEGIN_SRC html unordered lists heading in the page about plain-list #+END_SRC and is rendered like this [[#/docs/one-ox-plain-list/#unordered-lists][unordered lists heading in the page about plain-list]]. ** Fuzzy links I don't use ~fuzzy~ links. So, if there is a ~fuzzy~ link in the document, that means I wrote the link wrong. Broken links are bad user experience. I don't like them. So I decided that ~one-ox~ raises an error (hard-coded) when we try to export a fuzzy link to HTML. For instance, the following ~fuzzy~ link: #+BEGIN_SRC org [[fuzzy search]] #+END_SRC raise an error like the following: #+BEGIN_SRC emacs-lisp (one-link-broken "fuzzy search" "fuzzy links not supported" "goto-char: 5523") #+END_SRC ** File links *** Links to local files in assets and public directories Links to local files in ~./assets/~ and ~./public/~ directories like #+BEGIN_SRC org [[./assets/foo/bar.txt][Bar file]] [[./public/foo/baz.txt][Baz file]] #+END_SRC are exported with the prefixes ~./assets~ and ~./public~ of the path removed like this: #+BEGIN_SRC html Bar file Baz file #+END_SRC *** Local file links that raise one-link-broken error Any file link that doesn't point to a file in ~./assets/~ or ~./public/~ subdirectories raises an ~one-link-broken~ error when we try to export it with ~one-ox~ org backend For instance if we try to export using ~one-ox~ org backend the following link to the file ~foo.txt~ in the directory ~/tmp/~ #+BEGIN_SRC org [[/tmp/foo.txt]] #+END_SRC which is not in ~./public/~ subdirectory nor in ~./assets/~ subdirectory we will get an error like the following: #+BEGIN_SRC emacs-lisp (one-link-broken "/tmp/" "goto-char: 26308") #+END_SRC *** Links to images Links to local files in ~./assets/~ and ~./public/~ directories whom path matches ~one-ox-link-image-extensions~ regexp are exported with an ~img~ tag. For instance the following link to an image in ~./assets/img/~ directory #+BEGIN_SRC org [[./assets/img/keep-learning.png][Keep Learning]] #+END_SRC is exported as follow #+BEGIN_SRC html Keep Learning #+END_SRC and rendered like this [[./assets/img/keep-learning.png][Keep Learning]] * one-ox | plain-list and item :PROPERTIES: :ONE: one-default-doc :CUSTOM_ID: /docs/one-ox-plain-list/ :END: Only unordered and ordered lists are supported. ** Unordered lists :PROPERTIES: :CUSTOM_ID: /docs/one-ox-plain-list/#unordered-lists :END: The following org snippet (unordered list): #+BEGIN_SRC org - a thing, - another thing, - and the last one. #+END_SRC is exported by ~one-ox~ as follow #+BEGIN_SRC html #+END_SRC and is rendered like this: - a thing, - another thing, - and the last one. ** Ordered list The following org snippet (unordered list): #+BEGIN_SRC org 1. first, 2. second, 3. third. #+END_SRC is exported by ~one-ox~ as follow #+BEGIN_SRC html
  1. a thing,

  2. another thing,

  3. and the last one.

#+END_SRC and is rendered like this: 1. first, 2. second, 3. third. * one-ox | table :PROPERTIES: :ONE: one-default-doc :CUSTOM_ID: /docs/one-ox-table/ :END: Tables with header rows are supported. The following org snippet: #+BEGIN_SRC org | Name | Age | City | |-------+-----+----------| | Alice | 30 | New York | | Bob | 25 | London | #+END_SRC is exported by ~one-ox~ as follow #+BEGIN_SRC html
Name Age City
Alice 30 New York
Bob 25 London
#+END_SRC The first row is automatically exported as ~~ (header) tags when separated by a horizontal rule (~|---+---|~), and subsequent rows use ~~ (data) tags.