fido-node.github.io/posts/about_blog.html
2024-11-09 20:10:41 +00:00

528 lines
21 KiB
HTML

<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"/><meta author="Alex Mikhailov"/><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/><meta name="color-scheme" content="light dark"/><meta http-equiv="content-language" content="en-us"/><meta name="description" content="Blog post about publishing my blog with Org Mode"/><meta property="og:description" content="Blog post about publishing my blog with Org Mode"/><meta property="og:image" content="https://fidonode.me/resources/images/preview/posts/about_blog.org.png"/><meta property="og:title" content="Org to HTML and back"/><meta name="twitter:description" content="Blog post about publishing my blog with Org Mode"/><meta name="twitter:title" content="Org to HTML and back"/><meta name="twitter:image" content="https://fidonode.me/resources/images/preview/posts/about_blog.org.png"/><meta name="twitter:card" content="summary_large_image"/><link rel="icon" type="image/x-icon" href="/resources/favicon.ico"/><link rel="stylesheet" type="text/css" href="/resources/css/pico.sand.min.css"/><script defer="true" src="https://umami.dokutsu.xyz/script.js" data-website-id="d52d9af1-0c7d-4531-84c6-0b9c2850011f"></script><title>Org to HTML and back</title><link id="highlight-theme" rel="stylesheet" type="text/css"/><script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script><script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/languages/bash.min.js"></script><script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/languages/lisp.min.js"></script><script src="/resources/js/theme-selector.js"></script></head><body><header class="header"><div class="container"><nav><ul><li><strong>Alex Mikhailov</strong></li></ul><ul><li><a href="/index.html">About</a></li><li><a href="/posts.html">Blog</a></li><li><a href="/rss.xml">RSS</a></li></ul></nav></div></header><main class="container blog-post"><hgroup><h1>Org to HTML and back</h1><p>Blog post about publishing my blog with Org Mode</p><nav><ul><li>Tags:</li><li><mark><a href="/tags/@org-mode.html" class="secondary">@org-mode</a></mark></li></ul></nav></hgroup><div id="table-of-contents" role="doc-toc">
<h2>Table of Contents</h2>
<div id="text-table-of-contents" role="doc-toc">
<ul>
<li><a href="#org98c997d">Disclaimer</a></li>
<li><a href="#org52886b0">What is Org?</a></li>
<li><a href="#org96925dc">Why Org Mode?</a></li>
<li><a href="#org58ca260">Render Org to blog or whatever</a>
<ul>
<li><a href="#org7a403ab">Render HTML</a></li>
<li><a href="#org3f73524">Static files</a></li>
<li><a href="#orge802c30">Whole build script</a></li>
</ul>
</li>
<li><a href="#org721f45d">Publish through GitHub Action</a>
<ul>
<li><a href="#org3e853cb">Install Emacs</a></li>
<li><a href="#org9e433e6">Just bring everything</a></li>
<li><a href="#org6b1febc">BTW I use GNU Emacs</a></li>
</ul>
</li>
<li><a href="#orgffb06c3">What is next</a>
<ul>
<li><a href="#orgfe230fa">RSS Feed</a></li>
<li><a href="#org75be7c5">Open Graph image preview</a></li>
<li><a href="#org8e08eeb">Code highlighting</a></li>
</ul>
</li>
</ul>
</div>
</div>
<div id="outline-container-org98c997d" class="outline-2">
<h2 id="org98c997d">Disclaimer</h2>
<div class="outline-text-2" id="text-org98c997d">
<p>
I'm neither proficient in Org Mode (further on "Org"), nor a good front-end engineer. I think that a simple solution is better than no solution. If you see a mistake, you can contact me via <a href="mailto:iam@fidonode.me">iam@fidonode.me</a>.
</p>
</div>
</div>
<div id="outline-container-org52886b0" class="outline-2">
<h2 id="org52886b0">What is Org?</h2>
<div class="outline-text-2" id="text-org52886b0">
<blockquote>
<p>
Your life in plain text
</p>
<p>
A GNU Emacs major mode for keeping notes, authoring documents, computational notebooks, literate programming, maintaining to-do lists, planning projects, and more — in a fast and effective plain text system.
</p>
</blockquote>
<p>
Everything you can do in Org is to write a text. With a special markup, of course. This makes it versatile and extensible.
</p>
</div>
</div>
<div id="outline-container-org96925dc" class="outline-2">
<h2 id="org96925dc">Why Org Mode?</h2>
<div class="outline-text-2" id="text-org96925dc">
<ol class="org-ol">
<li>Plain text.
Plain text as a data source offers significant versatility. You can read and understand what happens in org files without needing Emacs.</li>
<li>Everything tool.
Org Mode is a planner with an agenda, a to-do list, a note-taking app, a Jupyter Notebook-like tool, and a zettelkasten tool. You can manage almost every aspect of your life with Org Mode.</li>
<li>The only way to eat an elephant is piece by piece.
I do not have a habit of collecting and keeping information. I believe that discovering other aspects of Org Mode will lead me to better note-taking practices.</li>
</ol>
</div>
</div>
<div id="outline-container-org58ca260" class="outline-2">
<h2 id="org58ca260">Render Org to blog or whatever</h2>
<div class="outline-text-2" id="text-org58ca260">
<p>
Org already has a way to render files into HTML, allowing you to create simple HTML files with minimal styling. I'm not interesting in styling from org, so I decide to use <a href="https://picocss.com">picocss</a> framework.
</p>
</div>
<div id="outline-container-org7a403ab" class="outline-3">
<h3 id="org7a403ab">Render HTML</h3>
<div class="outline-text-3" id="text-org7a403ab">
<p>
I want to change some templates here and there. I've found <code>esxml</code> package. It is a decent DSL for writing XML/HTML.
Here is how page header and footer look in this DSL.
</p>
<pre><code class="language-lisp">(defun my/header (info)
`(header (@ (class &quot;header&quot;))
(nav
(ul
(li
(strong
,(org-export-data (plist-get info :title) info))))
(ul
(li (a (@ (href &quot;/index.html&quot;)) &quot;About&quot;))
(li (a (@ (href &quot;/blog.html&quot;)) &quot;Blog&quot;))
(li (a (@ (href &quot;/rss.xml&quot;)) &quot;RSS&quot;))
)
))
)
(defun my/footer (info)
`(footer (@ (class &quot;footer&quot;))
(hr)
(p &quot;Alex Mikhailov&quot;)
(p &quot;Built with: &quot;
(a (@ (href &quot;https://www.gnu.org/software/emacs/&quot;)) &quot;GNU Emacs&quot;) &quot; &quot;
(a (@ (href &quot;https://orgmode.org/&quot;)) &quot;Org Mode&quot;) &quot; &quot;
(a (@ (href &quot;https://picocss.com/&quot;)) &quot;picocss&quot;)
)
))
</code></pre>
<p>
Looks neat for me. At least I don't need to mess with string concatenation.
Whole template wiring looks like that. Not much, but it works and easy to maintain.
</p>
<pre><code class="language-lisp">(defun my/template (contents info)
(concat
&quot;&amp;lt;!DOCTYPE html&amp;gt;&quot;
(sxml-to-xml
`(html (@ (lang &quot;en&quot;))
(head
(meta (@ (charset &quot;utf-8&quot;)))
(meta (@ (author &quot;Alex Mikhailov&quot;)))
(meta (@ (name &quot;viewport&quot;)
(content &quot;width=device-width, initial-scale=1, shrink-to-fit=no&quot;)))
(meta (@ (name &quot;color-scheme&quot;) (content &quot;light dark&quot;)))
(meta (@ (http-equiv &quot;content-language&quot;) (content &quot;en-us&quot;)))
(meta (@ (name &quot;description&quot;) (content &quot;Personal page with a blog about my technical adventures&quot;)))
(link (@ (rel &quot;icon&quot;) (type &quot;image/x-icon&quot;) (href &quot;/resources/favicon.ico&quot;)))
(link (@ (rel &quot;stylesheet&quot;) (href &quot;/resources/css/pico.sand.min.css&quot;)))
(script (@ (defer &quot;true&quot;) (src &quot;https://umami.dokutsu.xyz/script.js&quot;) (data-website-id &quot;d52d9af1-0c7d-4531-84c6-0b9c2850011f&quot;)) ())
(title ,(org-export-data (plist-get info :title) info)))
(body
(main (@ (class &quot;container&quot;))
,(my/header info)
(*RAW-STRING* ,contents)
,(my/footer info)
)
))
))
)
</code></pre>
<p>
Ok, now we need some additional steps to wire these templating function.
</p>
<pre><code class="language-lisp">;; Derive new backend with our custom tepmplating function
;; We derive it from regular HTML backend
(org-export-define-derived-backend &apos;my-html &apos;html
:translate-alist &apos;((template . my/template)
))
;; Define publish function which uses our freshly derived backend
(defun my/publish-to-html (plist filename pub-dir)
&quot;Publish an Org file to HTML using the custom backend.&quot;
(org-publish-org-to &apos;my-html filename &quot;.html&quot; plist pub-dir))
</code></pre>
<p>
So everything is almost done. Time to use our custom publishing function in projects list.
</p>
<pre><code class="language-lisp">(setq org-publish-project-alist
(list
(list &quot;blog&quot;
:recursive t
:base-directory my/blog-src-path
:publishing-directory my/web-export-path
:publishing-function &apos;my/publish-to-html
:html-html5-fancy t
:htmlized-source t
:with-author nil
:with-creator t
:with-toc t
:section-numbers nil
:time-stamp-file nil
)
))
</code></pre>
</div>
</div>
<div id="outline-container-org3f73524" class="outline-3">
<h3 id="org3f73524">Static files</h3>
<div class="outline-text-3" id="text-org3f73524">
<p>
Yep, you may want to publish some photos with your blog or any other static files.
</p>
<pre><code class="language-nil">(setq org-publish-project-alist
(list
(list &quot;static&quot;
:base-directory my/blog-src-path
:base-extension &quot;css\\|js\\|png\\|jpg\\|jpeg\\|gif\\|pdf\\|ico\\|txt&quot;
:publishing-directory my/web-export-path
:recursive t
:publishing-function &apos;org-publish-attachment
)
))
</code></pre>
<p>
Looks self explanatory.
</p>
</div>
</div>
<div id="outline-container-orge802c30" class="outline-3">
<h3 id="orge802c30">Whole build script</h3>
<div class="outline-text-3" id="text-orge802c30">
<p>
Here is the whole elisp script which I use to publish my blog. It have some additional quirks to work with <code class="src src-sh">doomscript ./build-site.el</code>.
</p>
<pre><code class="language-lisp">;; Load the publishing system
;; Configure environment
;;
(setq debug-on-error t)
(let ((default-directory (concat &quot;~/.config/emacs/.local/straight/build-&quot; emacs-version &quot;/&quot;)))
(normal-top-level-add-subdirs-to-load-path))
(add-to-list &apos;custom-theme-load-path
(concat &quot;~/.config/emacs/.local/straight/build-&quot; emacs-version &quot;/doom-themes&quot;))
(add-to-list &apos;custom-theme-load-path (concat &quot;~/.config/emacs/.local/straight/build-&quot; emacs-version &quot;/base16-theme&quot;))
(add-to-list &apos;custom-theme-load-path (concat &quot;~/.config/emacs/.local/straight/build-&quot; emacs-version &quot;/moe-theme&quot;))
(require &apos;xml)
(require &apos;dom)
(require &apos;ox-publish)
(require &apos;ox-rss)
(require &apos;org)
(require &apos;esxml)
;;
;;Variables
;;
(setq
my/url &quot;https://fidonode.me&quot;
my/web-export-path &quot;./public&quot;
my/blog-src-path &quot;./home/05 Blog&quot;
org-html-validation-link nil ;; Don&apos;t show validation link
org-html-htmlize-output-type &apos;inline-css
org-src-fontify-natively t)
;;
;;Templates
;;
(defun my/footer (info)
`(footer (@ (class &quot;footer&quot;))
(hr)
(p &quot;Alex Mikhailov&quot;)
(p &quot;Built with: &quot;
(a (@ (href &quot;https://www.gnu.org/software/emacs/&quot;)) &quot;GNU Emacs&quot;) &quot; &quot;
(a (@ (href &quot;https://orgmode.org/&quot;)) &quot;Org Mode&quot;) &quot; &quot;
(a (@ (href &quot;https://picocss.com/&quot;)) &quot;picocss&quot;)
)
))
(defun my/header (info)
`(header (@ (class &quot;header&quot;))
(nav
(ul
(li
(strong
,(org-export-data (plist-get info :title) info))))
(ul
(li (a (@ (href &quot;/index.html&quot;)) &quot;About&quot;))
(li (a (@ (href &quot;/blog.html&quot;)) &quot;Blog&quot;))
(li (a (@ (href &quot;/rss.xml&quot;)) &quot;RSS&quot;))
)
))
)
(defun my/template (contents info)
(concat
&quot;&amp;lt;!DOCTYPE html&amp;gt;&quot;
(sxml-to-xml
`(html (@ (lang &quot;en&quot;))
(head
(meta (@ (charset &quot;utf-8&quot;)))
(meta (@ (author &quot;Alex Mikhailov&quot;)))
(meta (@ (name &quot;viewport&quot;)
(content &quot;width=device-width, initial-scale=1, shrink-to-fit=no&quot;)))
(meta (@ (name &quot;color-scheme&quot;) (content &quot;light dark&quot;)))
(meta (@ (http-equiv &quot;content-language&quot;) (content &quot;en-us&quot;)))
(meta (@ (name &quot;description&quot;) (content &quot;Personal page with a blog about my technical adventures&quot;)))
(link (@ (rel &quot;icon&quot;) (type &quot;image/x-icon&quot;) (href &quot;/resources/favicon.ico&quot;)))
(link (@ (rel &quot;stylesheet&quot;) (href &quot;/resources/css/pico.sand.min.css&quot;)))
(script (@ (defer &quot;true&quot;) (src &quot;https://umami.dokutsu.xyz/script.js&quot;) (data-website-id &quot;d52d9af1-0c7d-4531-84c6-0b9c2850011f&quot;)) ())
(title ,(org-export-data (plist-get info :title) info)))
(body
(main (@ (class &quot;container&quot;))
,(my/header info)
(*RAW-STRING* ,contents)
,(my/footer info)
)
))
))
)
(org-export-define-derived-backend &apos;my-html &apos;html
:translate-alist &apos;((template . my/template)
))
(defun my/publish-to-html (plist filename pub-dir)
&quot;Publish an Org file to HTML using the custom backend.&quot;
(org-publish-org-to &apos;my-html filename &quot;.html&quot; plist pub-dir))
;;
;;Helpers
;;
(defun my/format-date-subtitle (file project)
&quot;Format the date found in FILE of PROJECT.&quot;
(format-time-string &quot;posted on %Y-%m-%d&quot; (org-publish-find-date file project)))
;;
;;Clear folder with results
;;
(when (file-directory-p my/web-export-path)
(delete-directory my/web-export-path t))
(mkdir my/web-export-path)
;;
;;Main blog configuration
;;
(setq org-publish-project-alist
(list
(list &quot;static&quot;
:base-directory my/blog-src-path
:base-extension &quot;css\\|js\\|png\\|jpg\\|jpeg\\|gif\\|pdf\\|ico\\|txt&quot;
:publishing-directory my/web-export-path
:recursive t
:publishing-function &apos;org-publish-attachment
)
(list &quot;blog&quot;
:recursive t
:base-directory my/blog-src-path
:publishing-directory my/web-export-path
:publishing-function &apos;my/publish-to-html
:html-html5-fancy t
:htmlized-source t
:with-author nil
:with-creator t
:with-toc t
:section-numbers nil
:time-stamp-file nil
)
))
;; Generate the site output
(org-publish-all t)
(message &quot;Build complete!&quot;)
</code></pre>
</div>
</div>
</div>
<div id="outline-container-org721f45d" class="outline-2">
<h2 id="org721f45d">Publish through GitHub Action</h2>
<div class="outline-text-2" id="text-org721f45d">
<p>
With all previous preparations, this step sounds simple like: <code class="src src-sh">emacs -Q --script ./build-site.el</code>
I've chosen a pretty standard way to publish static sites through GitHub Pages. Since I keep my Org files in a private repo, I need some additional steps to address it. I use the <code>peaceiris/actions-gh-pages@v3</code> action to publish from my Org repo to the Pages repo.
However, since I use <code>Doom Emacs</code> as my configuration framework, we need to address some more problems.
</p>
</div>
<div id="outline-container-org3e853cb" class="outline-3">
<h3 id="org3e853cb">Install Emacs</h3>
<div class="outline-text-3" id="text-org3e853cb">
<p>
If you want to run <code>Emacs Lisp</code>, you need the whole Emacs, at least without GUI. In a GitHub Action, you can simply run:
</p>
<pre><code class="language-sh">sudo apt install emacs-nox --yes
</code></pre>
<p>
This way has a downside - you will install Emacs on each action run since the system state is disposable.
</p>
</div>
</div>
<div id="outline-container-org9e433e6" class="outline-3">
<h3 id="org9e433e6">Just bring everything</h3>
<div class="outline-text-3" id="text-org9e433e6">
<p>
I need to take extra steps since I use Doom Emacs and have my configs in Org. You may also need to install dependencies for your configuration.
</p>
<p>
Fetch doom guts
</p>
<pre><code class="language-sh">git clone --depth 1 https://github.com/doomemacs/doomemacs ~/.config/emacs
</code></pre>
<p>
Prepare minimal config for rendering Org file to config.
</p>
<pre><code class="language-sh">echo &apos;(doom! :config literate)&apos; &amp;gt; ~/.config/doom/init.el
echo &apos;(setq +literate-config-file &quot;&apos;$(pwd)&apos;/config/config.org&quot;)&apos; &amp;gt; ~/.config/doom/cli.el
</code></pre>
<p>
Finally, install all dependencies according to my config. Yes, it is overhead, but I can be sure that I have the same dependencies as on my dev machine.
</p>
<pre><code class="language-sh">~/.config/emacs/bin/doom sync -B
</code></pre>
<p>
Of course, I use a caching step to make the whole process faster:
</p>
<pre><code class="language-yaml">- name: Cache doom-emacs
uses: actions/cache@v4
id: cache-doom-save
with:
path: ~/.config/emacs
key: ${{ runner.os }}-doom
</code></pre>
</div>
</div>
<div id="outline-container-org6b1febc" class="outline-3">
<h3 id="org6b1febc">BTW I use GNU Emacs</h3>
<div class="outline-text-3" id="text-org6b1febc">
<p>
Here's the whole publishing workflow.
</p>
<pre><code class="language-yaml">name: pages
on:
push:
branches:
- &quot;main&quot;
# Do not trigger build on changes in other folders
paths-ignore:
- &quot;./home/02 Action&quot;
- &quot;./home/03 PKM&quot;
- &quot;./home/04 Log&quot;
- &quot;./home/06 Projects&quot;
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out
uses: actions/checkout@v1
#Install emacs without GUI components
- name: Install Emacs
run: sudo apt install emacs-nox --yes
#Clone doomemacs. Yep, always the most fresh master. Let it fire.
- name: Install doom
run: git clone --depth 1 https://github.com/doomemacs/doomemacs ~/.config/emacs
# Use cached files to shave some time
- name: Restore cached doom-emacs
id: cache-doom-restore
uses: actions/cache/restore@v4
with:
path: ~/.config/emacs
key: ${{ runner.os }}-doom
- name: Create folder
run: mkdir -p ~/.config/doom/
# I use literate config, so we need some extra steps to botstrap my config
- name: Put template for literate config
run: echo &apos;(doom! :config literate)&apos; &amp;gt; ~/.config/doom/init.el
# Yep. I also keep my emacs config in org in my org repo
- name: Propagate org conf
run: echo &apos;(setq +literate-config-file &quot;&apos;$(pwd)&apos;/config/config.org&quot;)&apos; &amp;gt; ~/.config/doom/cli.el
# Build doomemacs deps. Should be relativelly fast, cause almost everything cached.
- name: Sync doom
run: ~/.config/emacs/bin/doom sync -B
#Put files into cache
- name: Cache doom-emacs
uses: actions/cache@v4
id: cache-doom-save
with:
path: ~/.config/emacs
key: ${{ runner.os }}-doom
- name: Build the site
run: ~/.config/emacs/bin/doomscript ./build-site.el
# Deploy from this repo to that ~external_repository~
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
deploy_key: ${{ secrets.PRIVATE_KEY }}
external_repository: fido-node/fido-node.github.io
publish_branch: gh-pages
publish_dir: ./public
</code></pre>
</div>
</div>
</div>
<div id="outline-container-orgffb06c3" class="outline-2">
<h2 id="orgffb06c3">What is next</h2>
<div class="outline-text-2" id="text-orgffb06c3">
<p>
I have a plans to make posts about next features:
</p>
</div>
<div id="outline-container-orgfe230fa" class="outline-3">
<h3 id="orgfe230fa"><a href="./add_rss_to_blog.html">RSS Feed</a></h3>
</div>
<div id="outline-container-org75be7c5" class="outline-3">
<h3 id="org75be7c5"><a href="./posts_preview.html">Open Graph image preview</a></h3>
</div>
<div id="outline-container-org8e08eeb" class="outline-3">
<h3 id="org8e08eeb"><a href="./improve_code_blocks.html">Code highlighting</a></h3>
</div>
</div>
</main><footer class="footer"><div class="container"><hr/><small><p>Alex Mikhailov</p><p>Built with: <a href="https://www.gnu.org/software/emacs/">GNU Emacs</a> <a href="https://orgmode.org/">Org Mode</a> <a href="https://picocss.com/">picocss</a></p></small></div></footer></body></html>