Skip to content

Instantly share code, notes, and snippets.

@spookylukey
Last active August 20, 2024 18:26
Show Gist options
  • Save spookylukey/1ca5e3ae4bac4f0431ecc70bff841902 to your computer and use it in GitHub Desktop.
Save spookylukey/1ca5e3ae4bac4f0431ecc70bff841902 to your computer and use it in GitHub Desktop.
;; My Emacs blogging setup:
;;
;; - nikola - https://getnikola.com/
;; with some extensions:
;; - orgmode - https://plugins.getnikola.com/v8/orgmode/
;; - sass - https://plugins.getnikola.com/v7/sass/
;; - ditaa - custom - https://gist.github.com/spookylukey/e25c1d9d99acacd776029c01a50337c6
;; - shell - custom - https://gist.github.com/spookylukey/c175709610d5466f1bba49f356f505a1
;;
;; - Mostly reStructuredText for markup. It's powerful, and has good support for syntax extensions
;; - I use a custom "shell" directive that allows inserting output of arbitrary shell commands, see above
;; - I have some editing helpers for some of its more verbose constructs, see below
;;
;; - occasionally orgmode for some pages, see https://plugins.getnikola.com/v8/orgmode/
;;
;; - an `org-to-html` command that will convert org-mode markup to HTML (just an alias for `pandoc -f org -t html`).
;; I use this in conjunction with the `shell` custom directive when I want to use org-mode markup
;; in a restructuredtext post, e.g. to use org-mode tables
;; e.g. for this blog post: https://lukeplant.me.uk/blog/posts/life-on-the-diagonal-adventures-in-2d-time/
;; I have code like this:
;;
;; .. shell:: org-to-html
;; :format: html
;;
;; | ~id~ | ~customer~ | ~event_id~ | ~recorded_at~ | ~charged_at~ | ~amount~ | ~description~ |
;; |------+------------+--------------------------+---------------+--------------+----------+-------------------------------|
;; | 1 | 1 | payment-1 | 2021-01-09 | 2021-01-09 | 100 | Credit card payment |
;; | 2 | 1 | subscription-123-month-1 | 2021-01-10 | 2021-01-10 | -10 | Basic email plan |
;; | 3 | 1 | subscription-123-month-1 | 2021-01-25 | 2021-01-10 | -8 | Basic email plan (discounted) |
;; | 4 | 1 | subscription-123-month-2 | 2021-02-10 | 2021-02-10 | -8 | Basic email plan (discounted) |
;; I switch modes for editing the org-mode table
;; The following helper functions for content:
(defun my/new-blog-post ()
(interactive)
(let
((title (read-string "Post title: "))
(tags (completing-read-multiple "Enter tags, comma separated: " my/blog-tag-options nil nil nil 'blog-post-tags-history)))
(find-file (concat "/home/luke/devel/lukeplant.me.uk/src/content/blog/" (my/filename-safe title) ".rst"))
(insert ".. title: " title "\n")
(insert ".. slug: " (my/slugify title) "\n")
(insert ".. date: " (format-time-string "%Y-%m-%dT%H:%M:%S%z" (current-time)) "\n")
(insert ".. tags: " (mapconcat 'identity tags ", ") "\n")
(insert ".. status: draft\n")
(insert ".. description:\n")
(insert ".. type: text\n\n")))
(defvar my/blog-tag-options
;; TODO automatically populate this from posts, or make new items added
;; automatically. Currently I have to edit manually.
'("Apologetics"
"Artificial Intelligence"
"blockchain"
"Blogging and bloggers"
"Books"
"CCIW"
"ChatGPT"
"Christianity"
"Django"
"Django Admin"
"draft"
"Elm"
"Emacs"
"Environment"
"Facebook posts"
"Fractals"
"Haskell"
"Internet"
"IPython"
"KDE"
"Kio-Sword"
"Linux"
"lukeplant.me.uk"
"Microsoft"
"Music"
"Personal and misc"
"PHP"
"Python"
"Python type hints"
"Rants"
"Security"
"Software development"
"Software projects"
"Web development"))
(defun my/slugify (title)
"Convert TITLE to a slug (no spaces, all lowercase)"
(replace-regexp-in-string
"-$" ""
(replace-regexp-in-string
"-+" "-"
(replace-regexp-in-string
"[^a-z0-9-]" "-"
(replace-regexp-in-string
"'" ""
(downcase title))))))
(defun my/filename-safe (filename)
(replace-regexp-in-string "[<>:\"\/\\|?*\x00-\x1F]" "" filename))
(defun my/get-blog-slug ()
"Get current blog post slug"
(let ((slug-lines ""))
(save-excursion
(goto-char (point-min))
(re-search-forward "^\\.\\. slug:." nil t)
(buffer-substring-no-properties (point) (line-end-position)))))
;; I don't actually use this much
(defun my/insert-blog-previewimage ()
(interactive)
(let* ((slug (my/get-blog-slug))
(url (concat "http://lukeplant.local:9111/blog/posts/" slug "/"))
(previewimage-name (concat "/blogmedia/previews/" slug ".png"))
(output-image (shell-quote-argument (concat "../../files" previewimage-name))))
(progn
(shell-command (concat "shot-scraper " url " -o " output-image " --width 600 --height 415"))
;; Crop the site boilerplate, currently 100px
(shell-command (concat "convert " output-image " -crop 600x315+0+100 " output-image))
(goto-char (point-min))
(re-search-forward "^\\.\\. slug: .*$")
(insert "\n.. previewimage: " previewimage-name))))
;; Helpers for rst-mode:
;; - borrowed from Markdown - type ``` <TAB> and get `.. code-block::`
;; - use `img` <TAB> as shortcut for inserting images
(use-package rst-mode
:commands (rst-mode)
:init
(add-hook 'rst-mode-hook
(lambda ()
(use-flyspell)
(typo-mode)
(local-unset-key [f7])
(local-set-key [f7] 'my/browse-html-for-rst-buffer)
(local-set-key (kbd "<tab>") #'my/rst-tab))))
(use-package typo
:commands (org-mode rst-mode markdown-mode)
:config
;; Override typo-mode-map for ` and - which are usually unhelpful
(define-key typo-mode-map (kbd "`") 'self-insert-command)
(define-key typo-mode-map (kbd "-") 'self-insert-command)
)
(defun my/rst-tab ()
"Checks for some custom shortcuts:
- if the last 3 characters are ``` replaces them with a code block.
- if the last 3 characters are img replace with .. image
Otherwise, performs the default tab behavior."
(interactive)
(if (and (>= (point) 3)
(string-equal "```" (buffer-substring (- (point) 3) (point))))
(progn
(delete-backward-char 3)
(insert ".. code-block:: ")
(insert (completing-read "Language: " my/rst-code-block-languages))
(insert "\n\n "))
(if (and (>= (point) 3)
(string-equal "img" (buffer-substring (- (point) 3) (point))))
(progn
(delete-backward-char 3)
(insert ".. image:: ")
(my/insert-file-name-from-filesystem)
(insert "\n :align: center\n"))
;; Fallback to normal tab
(indent-for-tab-command))))
(defcustom my/rst-code-block-languages (list "python" "html" "json" "shell" "javascript" "html+django" "css" "xml")
"Languages used for `.. code-block::`"
:type '(repeat string))
(defun my/insert-rst-local-link ()
"Prompt user for local file name and insert into buffer as an RST link"
(interactive)
(let* ((absolute-file-name (read-file-name "Select file: "))
(relative-file-name (file-relative-name absolute-file-name))
(start (point)))
(insert (concat "` <./" relative-file-name ">`_"))
(goto-char start)
(right-char 1)))
(defun my/insert-file-name-from-filesystem ()
"Prompt user to select a file and insert its relative name into the buffer."
(interactive)
(let* ((absolute-file-name (read-file-name "Select file: "))
(relative-file-name (file-relative-name absolute-file-name)))
(insert relative-file-name)))
;; General - helps with some of the above:
;; Vertical completion in the minibuffer:
(use-package vertico
:init (vertico-mode))
;; Better ordering of items in vertico, remembering recently chosen items
(use-package vertico-prescient
:init (vertico-prescient-mode))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment