Last active
August 20, 2024 18:26
-
-
Save spookylukey/1ca5e3ae4bac4f0431ecc70bff841902 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;; 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