Compare commits
30 commits
literate-b
...
master
Author | SHA1 | Date | |
---|---|---|---|
9c3d48cce6 | |||
a8d8799aca | |||
7a71848d00 | |||
f04d514adf | |||
d3b5bdcc46 | |||
9156f202f3 | |||
0739882bfa | |||
2a68a3850c | |||
334b8b6a05 | |||
99c5ca9fb2 | |||
0ccaa9d08d | |||
96218b1aa6 | |||
a8b6f534bc | |||
0c784bd3a8 | |||
3b92ec31f8 | |||
0875226500 | |||
13a9b718b8 | |||
fe7933869e | |||
2536e9add5 | |||
317c679082 | |||
7f17632ab0 | |||
aa1f297dc5 | |||
409897a830 | |||
5e6e4adefe | |||
6d35e55251 | |||
a22ed2e878 | |||
3f21b156d7 | |||
813ed5acc3 | |||
370c1e3bbf | |||
5bca336146 |
20 changed files with 643 additions and 432 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,11 +1,14 @@
|
|||
/.cask
|
||||
/.org-timestamps
|
||||
/.sass-cache
|
||||
/.emacs-local
|
||||
|
||||
# Built files
|
||||
/public
|
||||
/posts/index.org
|
||||
/build.mk
|
||||
/Eldev
|
||||
/publish.el
|
||||
|
||||
# Added automatically by `eldev init'.
|
||||
/.eldev
|
||||
|
|
12
GNUmakefile
12
GNUmakefile
|
@ -1,11 +1,7 @@
|
|||
define tangle =
|
||||
eldev emacs -quick -batch \
|
||||
-eval "(package-initialize)" \
|
||||
build.mk: literate-build.org
|
||||
HOME=".emacs-local" emacs -quick -batch \
|
||||
-eval "(setq vc-handled-backends nil)" \
|
||||
-load ob-tangle \
|
||||
-eval "(org-babel-tangle-file \"$<\" \"$(PWD)/$@\"$(if $1, \"$1\"))"
|
||||
endef
|
||||
|
||||
%.mk: build.org
|
||||
$(call tangle)
|
||||
-eval "(org-babel-tangle-file \"$<\"))"
|
||||
|
||||
include build.mk
|
||||
|
|
167
build.org
167
build.org
|
@ -1,167 +0,0 @@
|
|||
#+title: ryuslash's website's build's files
|
||||
#+subtitle: Literate configuration of how to build this site
|
||||
#+options: num:nil prop:t
|
||||
|
||||
I'm a big fan of [[file:literate-programming.org][Literate Programming]], so I figured I'd make the builds for my website a literate configuration file as well.
|
||||
|
||||
First I need to build the build files. This is the smallest make file that I can think to make to enable me to build the rest out. This make file is duplicated in both this file and the source code repository since I don't know of a way not to.
|
||||
|
||||
First I define a function =tangle= that can be used to tangle an org file to the code file that it needs to be. Then I specify that any =.mk= file should depend on /this/ file ({{{input-file}}}) and that it is generated by running =org-babel-tangle-file= on it. The =$<= is the first dependency and =$@= is the current target file (whatever =.mk= file we're generating).
|
||||
|
||||
#+begin_src makefile-gmake :tangle GNUmakefile
|
||||
define tangle =
|
||||
eldev emacs -quick -batch \
|
||||
-eval "(package-initialize)" \
|
||||
-load ob-tangle \
|
||||
-eval "(org-babel-tangle-file \"$<\" \"$(PWD)/$@\"$(if $1, \"$1\"))"
|
||||
endef
|
||||
|
||||
%.mk: build.org
|
||||
$(call tangle)
|
||||
#+end_src
|
||||
|
||||
After that it's just a matter of including the file I want.
|
||||
|
||||
#+begin_src makefile-gmake :tangle GNUmakefile
|
||||
include build.mk
|
||||
#+end_src
|
||||
|
||||
GNU Make (I don't know about other makes) will see if there is a recipe to make the file it wants to include and will try and run it before trying to include the file. This combined with our =%.mk= target ensures that make will always try to recreate the =build.mk= file when {{{input-file}}} is updated.
|
||||
|
||||
* Makefile
|
||||
:PROPERTIES:
|
||||
:header-args:makefile-gmake: :tangle build.mk
|
||||
:END:
|
||||
|
||||
This is the actual make file that builds and deploys my site. It's all put into the =build.mk= file and executed from there. The =%.mk= pattern rule thankfully doesn't get recognized as a make target, so the first target define in the included file is assumed to be the default target.
|
||||
|
||||
First off I specify the =help= target. This target parses the make files and extracts targets that include some comment on what they do. This target should come first so that it automatically becomes the default target. This way when I run just ~make~ I can see which targets I have available. I got this awesome trick from [[https://victoria.dev/][Victoria Drake]]’s article [[https://victoria.dev/blog/how-to-create-a-self-documenting-makefile/][How to create a self-documenting Makefile]].
|
||||
|
||||
#+begin_src makefile-gmake
|
||||
help: ## Show this help
|
||||
@grep --extended-regexp --no-filename '^[^#].*?\s##\s' $(MAKEFILE_LIST) \
|
||||
| sort \
|
||||
| awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
|
||||
#+end_src
|
||||
|
||||
The =build= target converts everything from whatever source files they are to html and css. The build target has 2 other targets it depends on, not surprisingly =html= and =css=. The =html= and =css= targets don't have any comment because they're not really meant to be executed directly.
|
||||
|
||||
#+begin_src makefile-gmake
|
||||
build: html css ## Build the site and copy it to the staging directory
|
||||
#+end_src
|
||||
|
||||
The =html= target calls Emacs. It depends on the =Eldev= file having been generated to specify any additional dependencies and which package archives should be used and this file will be generated by the =Eldev= target.
|
||||
|
||||
#+begin_src makefile-gmake
|
||||
html: Eldev
|
||||
@echo "Publishing..."
|
||||
eldev emacs --quick --batch --load publish.el --funcall org-publish-all
|
||||
#+end_src
|
||||
|
||||
The =css= target does specify its dependencies. This is both an exercise in writing make files (which I generally quite enjoy), and also to make sure that my builds don't take too long unless they actually have to. Ultimately any =.css= file gets created from a =.less= file by calling the =lessc= program. I'm intentionally not using recursive make in this project because it slows make down a lot, and I don't have to manage several make files this way.
|
||||
|
||||
#+begin_src makefile-gmake
|
||||
css: public/assets/css/main.css public/assets/css/tekuti.css public/assets/css/cgit.css
|
||||
|
||||
public/assets/css/main.css public/assets/css/tekuti.css public/assets/css/cgit.css: \
|
||||
src/less/include/common.less \
|
||||
src/less/include/components.less \
|
||||
src/less/include/colors.less \
|
||||
src/less/yoshi.css
|
||||
|
||||
public/assets/css/%.css: src/less/%.less
|
||||
lessc $< $@
|
||||
#+end_src
|
||||
|
||||
The =deploy= target first makes sure that =build= has been executed at least once and then uses =rsync= to upload all of the files. This intentionally doesn't depend on the =build= target so that I can upload whatever I happen to have generated without being forced to rebuild.
|
||||
|
||||
#+begin_src makefile-gmake
|
||||
deploy: ## Deploy the site to live
|
||||
@[[ -e public/index.html ]] || (echo "Run 'make build' before deploy" && exit 1)
|
||||
rsync --verbose --checksum --recursive --delete \
|
||||
--exclude '*~' --exclude '.eldev' --delete-excluded \
|
||||
public/ ryuslash.org:ryuslash-next/
|
||||
#+end_src
|
||||
|
||||
The =clean= target makes sure that everything that is generated gets cleaned up again. This is important if I need to start with a clean slate.
|
||||
|
||||
#+begin_src makefile-gmake
|
||||
clean: ## Remove all of the build files
|
||||
@echo "Cleaning up..."
|
||||
@rm -rvf *.elc
|
||||
@rm -rvf public
|
||||
@rm -rvf .org-timestamps
|
||||
@rm -rvf posts/index.org build.mk Eldev
|
||||
#+end_src
|
||||
|
||||
The =serve= target is a convenience target for when I'm writing or making modifications to the build and publish processes. It just starts a simple =php= web server in the =public/= directory so that I can easily load it in my browser.
|
||||
|
||||
#+begin_src makefile-gmake
|
||||
serve: ## Run a simple web server to look at the results
|
||||
@cd public && php -S "localhost:8000"
|
||||
#+end_src
|
||||
|
||||
The =theme= target is another convenience target. I generate the colors for the source code blocks on my site from my Emacs theme. This target exports the colors from my theme so that the code blocks can use them. This file is then included by the less files. There is no good dependency here, because there is no file for the export of my theme to depend on right now, just occasionally I have to run it. It does depend on the =Eldev= file having been generated.
|
||||
|
||||
#+begin_info
|
||||
I keep this particular target around for playing with, but right now it doesn't actually work. Just because even though I load and enable =yoshi-theme= it doesn't appear to apply the theme in a batch session. Seems understandable because no UI actually gets loaded, but that does mean that it can't figure out which faces it sets and it just outputs the colors for the default theme.
|
||||
#+end_info
|
||||
|
||||
#+begin_src makefile-gmake
|
||||
theme: Eldev ## Generate the theme CSS
|
||||
eldev emacs --quick --batch --load htmlize --load ox-html \
|
||||
-eval "(setq org-html-htmlize-output-type 'css)" \
|
||||
-funcall org-html-htmlize-generate-css \
|
||||
-load yoshi-theme \
|
||||
-eval "(enable-theme 'yoshi)" \
|
||||
-load make-mode \
|
||||
-eval "(kill-whole-line)" \
|
||||
-eval "(kill-whole-line)" \
|
||||
-eval "(goto-char (point-max))" \
|
||||
-eval "(forward-line -2)" \
|
||||
-eval "(kill-whole-line)" \
|
||||
-eval "(kill-whole-line)" \
|
||||
-eval "(css-mode)" \
|
||||
-eval "(indent-region (point-min) (point-max))" \
|
||||
-eval '(write-file "src/less/yoshi.css")'
|
||||
#+end_src
|
||||
|
||||
The =Eldev= target just tangles the {{{input-file}}} into the =Eldev= file. This uses the =tangle= function defined in the intro section.
|
||||
|
||||
#+begin_src makefile-gmake
|
||||
Eldev: build.org
|
||||
$(call tangle)
|
||||
#+end_src
|
||||
|
||||
Finally, as a precaution, I specify that all of the main targets are phony targets. This way if I ever introduce any file with the same name as these targets they will still build and not assume that because the file exists, everything is up-to-date.
|
||||
|
||||
#+begin_src makefile-gmake
|
||||
.PHONY: publish deploy html css help theme
|
||||
#+end_src
|
||||
|
||||
* Eldev
|
||||
:PROPERTIES:
|
||||
:header-args:emacs-lisp: :tangle Eldev
|
||||
:END:
|
||||
|
||||
With Eldev I can install dependencies of my =publish.el= locally. Really the only reason I chose to use Eldev is because it is available in the [[file:guix.org][Guix]] repository.
|
||||
|
||||
Since this is an actually full-fledged Emacs Lisp file and I don't know what I'm going to be doing in the future, I should be sure to enable lexical binding. Otherwise Emacs defaults to using dynamic binding, and that might cause some surprises in the future, since I'm quite used to using lexical binding everywhere.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
; -*- lexical-binding: t -*-
|
||||
#+end_src
|
||||
|
||||
All I'm doing here, really, is enable the various Emacs Lisp Package Archives.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(eldev-use-package-archive 'gnu)
|
||||
(eldev-use-package-archive 'nongnu)
|
||||
(eldev-use-package-archive 'melpa)
|
||||
#+end_src
|
||||
|
||||
* COMMENT Local Variables
|
||||
|
||||
# Local Variables:
|
||||
# org-src-preserve-indentation: t
|
||||
# End:
|
|
@ -6,3 +6,12 @@
|
|||
I write all the content for this site and for [[https://blog.ryuslash.org/][my blog]] in Emacs. I try to do all of my work in it, although it doesn’t always work out. I have a specific [[https://blog.ryuslash.org/tags/emacs][tag]] for it on my blog with a [[https://blog.ryuslash.org/feed/atom?with=emacs][feed]] you can subscribe to in case you don’t care about the rest of it.
|
||||
|
||||
As I started reading [[https://blog.calebjay.com/posts/my-emacs-environment/][How I Write Code, Take Notes, Journal, Track Time and Tasks, and Stay Organized using Emacs]] by [[https://www.calebjay.com/][Caleb Jay Rogers]] I was inspired to write a little more about my own Emacs experience and origins. I had just started using GNU/Linux on my desktop and was looking for a good IDE or text editor. I tried [[https://www.eclipse.org/][Eclipse]], [[https://www.codeblocks.org/][Code::Blocks]], [[https://www.oracle.com/tools/technologies/netbeans-ide.html/][NetBeans]], and of course [[https://www.vim.org/][Vim]]. I was specifically looking for one that worked well from a terminal window and supported nice looking syntax highlighting. So really it ended up being down to either Vim or Emacs. At the time I didn’t really like either much. I would always get stuck in Vim and I couldn’t understand this weird Lisp language that Emacs used for its configuration. And the keybindings were so weird!
|
||||
|
||||
* Related posts
|
||||
|
||||
- [[https://blog.ryuslash.org/archives/2023/12/07/til-i-can-use-elfeed-search-face-alist-to-highlight-certain-headlines-in-elfeed][TIL: I can use elfeed-search-face-alist to highlight certain headlines in Elfeed]]
|
||||
- [[https://blog.ryuslash.org/archives/2023/07/10/switch-todo-state-when-clocking-in][Switch TODO state when clocking in]]
|
||||
- [[https://blog.ryuslash.org/archives/2023/04/12/ielm-paredit][IELM & Paredit]]
|
||||
- [[https://blog.ryuslash.org/archives/2021/09/11/combining-shell-and-lisp-in-eshell][Combining Shell and Lisp in Eshell]]
|
||||
- [[https://blog.ryuslash.org/archives/2021/07/04/loading-the-emacs-info-manuals-in-msys2][Loading the Emacs Info manuals in MSYS2]]
|
||||
- [[https://blog.ryuslash.org/archives/2021/06/04/olivetti-mode][olivetti-mode]]
|
||||
|
|
BIN
favicon.ico
Normal file
BIN
favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
|
@ -3,10 +3,8 @@
|
|||
#+OPTIONS: toc:nil num:nil
|
||||
#+STARTUP: showall
|
||||
#+STARTUP: inlineimages
|
||||
|
||||
#+begin_main-navigation
|
||||
[[https://ryuslash.org][home]] | [[https://code.ryuslash.org][code]] | [[https://blog.ryuslash.org][blog]]
|
||||
#+end_main-navigation
|
||||
#+html_link_up: /
|
||||
#+html_link_home: /
|
||||
|
||||
#+begin_introduction
|
||||
#+ATTR_HTML: :id avatar :alt Angry black prinny (looking like Tux), playing on a PSP
|
||||
|
@ -29,7 +27,7 @@ The first topic I pay any attention to of course has to be [[file:emacs.org][Ema
|
|||
|
||||
* My literate configuration
|
||||
|
||||
Every so often I get really drawn to remaking my entire configuration into a [[file:literate-programming.org][Literate Programming]] style. See my [[file:config/rincewind.org][latest attempt]] and also my [[file:build.org][literate build files]] for this website.
|
||||
Every so often I get really drawn to remaking my entire configuration into a [[file:literate-programming.org][Literate Programming]] style. See my [[file:config/rincewind.org][latest attempt]] and also my [[file:literate-build.org][literate build files]] for this website.
|
||||
|
||||
* Topics
|
||||
|
||||
|
|
449
literate-build.org
Normal file
449
literate-build.org
Normal file
|
@ -0,0 +1,449 @@
|
|||
#+title: ryuslash's website's build's files
|
||||
#+subtitle: Literate configuration of how to build this site
|
||||
#+options: num:nil prop:t
|
||||
#+macro: kbd @@html:<kbd>@@$1@@html:</kbd>@@
|
||||
|
||||
I'm a big fan of [[file:literate-programming.org][Literate Programming]], so I figured I'd make the builds for my website a literate configuration file as well.
|
||||
|
||||
First I need to build the build files. This is the smallest make file that I can think to make to enable me to build the rest out. This make file is duplicated in both this file and the source code repository since I don't know of a way not to.
|
||||
|
||||
I specify that =build.mk= file should depend on /this/ file ({{{input-file}}}) and that it is generated by running =org-babel-tangle-file= on it. The =$<= is the first dependency and =$@= is the current target file (whatever =.mk= file we're generating).
|
||||
|
||||
#+begin_src makefile-gmake :tangle GNUmakefile
|
||||
build.mk: literate-build.org
|
||||
HOME=".emacs-local" emacs -quick -batch \
|
||||
-eval "(setq vc-handled-backends nil)" \
|
||||
-load ob-tangle \
|
||||
-eval "(org-babel-tangle-file \"$<\"))"
|
||||
#+end_src
|
||||
|
||||
After that it's just a matter of including the file I want.
|
||||
|
||||
#+begin_src makefile-gmake :tangle GNUmakefile
|
||||
include build.mk
|
||||
#+end_src
|
||||
|
||||
GNU Make (I don't know about other makes) will see if there is a recipe to make the file it wants to include and will try and run it before trying to include the file. This combined with our =%.mk= target ensures that make will always try to recreate the =build.mk= file when {{{input-file}}} is updated.
|
||||
|
||||
* Makefile
|
||||
:PROPERTIES:
|
||||
:header-args:makefile-gmake: :tangle build.mk
|
||||
:END:
|
||||
|
||||
This is the actual make file that builds and deploys my site. It's all put into the =build.mk= file and executed from there. The =%.mk= pattern rule thankfully doesn't get recognized as a make target, so the first target define in the included file is assumed to be the default target.
|
||||
|
||||
First off I specify the =help= target. This target parses the make files and extracts targets that include some comment on what they do. This target should come first so that it automatically becomes the default target. This way when I run just ~make~ I can see which targets I have available. I got this awesome trick from [[https://victoria.dev/][Victoria Drake]]’s article [[https://victoria.dev/blog/how-to-create-a-self-documenting-makefile/][How to create a self-documenting Makefile]].
|
||||
|
||||
#+begin_src makefile-gmake
|
||||
help: ## Show this help
|
||||
@grep --extended-regexp --no-filename '^[^#].*?\s##\s' $(MAKEFILE_LIST) \
|
||||
| sort \
|
||||
| awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
|
||||
#+end_src
|
||||
|
||||
The =build= target converts everything from whatever source files they are to html and css. The build target has 2 other targets it depends on, not surprisingly =html= and =css=. The =html= and =css= targets don't have any comment because they're not really meant to be executed directly.
|
||||
|
||||
#+begin_src makefile-gmake
|
||||
build: html css ## Build the site and copy it to the staging directory
|
||||
#+end_src
|
||||
|
||||
The =html= target calls Emacs.
|
||||
|
||||
#+begin_src makefile-gmake
|
||||
html:
|
||||
@echo "Publishing..."
|
||||
HOME=".emacs-local" emacs --quick --batch --load publish.el --funcall org-publish-all
|
||||
#+end_src
|
||||
|
||||
The =css= target does specify its dependencies. This is both an exercise in writing make files (which I generally quite enjoy), and also to make sure that my builds don't take too long unless they actually have to. Ultimately any =.css= file gets created from a =.scss= file by calling the =scss= program. I'm intentionally not using recursive make in this project because it slows make down a lot, and I don't have to manage several make files this way.
|
||||
|
||||
#+begin_src makefile-gmake
|
||||
css: public/assets/css/main.css public/assets/css/tekuti.css public/assets/css/cgit.css
|
||||
|
||||
public/assets/css/main.css public/assets/css/tekuti.css public/assets/css/cgit.css: \
|
||||
src/scss/include/_common.scss \
|
||||
src/scss/include/_components.scss \
|
||||
src/scss/include/_colors.scss \
|
||||
src/scss/include/_yoshi.scss
|
||||
|
||||
public/assets/css/%.css: src/scss/%.scss
|
||||
mkdir -p $$(dirname $@)
|
||||
scss $< $@
|
||||
#+end_src
|
||||
|
||||
The =deploy= target first makes sure that =build= has been executed at least once and then uses =rsync= to upload all of the files. This intentionally doesn't depend on the =build= target so that I can upload whatever I happen to have generated without being forced to rebuild.
|
||||
|
||||
#+begin_src makefile-gmake
|
||||
deploy: ## Deploy the site to live
|
||||
@[[ -e public/index.html ]] || (echo "Run 'make build' before deploy" && exit 1)
|
||||
rsync --verbose --checksum --recursive --delete \
|
||||
--exclude '*~' --exclude '.emacs-local' --exclude 'posts/' \
|
||||
--delete-excluded \
|
||||
public/ ryuslash.org:ryuslash-next/
|
||||
#+end_src
|
||||
|
||||
The =clean= target makes sure that everything that is generated gets cleaned up again. This is important if I need to start with a clean slate.
|
||||
|
||||
#+begin_src makefile-gmake
|
||||
clean: ## Remove all of the build files
|
||||
@echo "Cleaning up..."
|
||||
@rm -rvf *.elc
|
||||
@rm -rvf public
|
||||
@rm -rvf .org-timestamps
|
||||
@rm -rvf posts/index.org build.mk .emacs-local publish.el
|
||||
#+end_src
|
||||
|
||||
The =serve= target is a convenience target for when I'm writing or making modifications to the build and publish processes. It just starts a simple =php= web server in the =public/= directory so that I can easily load it in my browser.
|
||||
|
||||
#+begin_src makefile-gmake
|
||||
serve: ## Run a simple web server to look at the results
|
||||
@cd public && php -S "localhost:8000"
|
||||
#+end_src
|
||||
|
||||
The =theme= target is another convenience target. I generate the colors for the source code blocks on my site from my Emacs theme. This target exports the colors from my theme so that the code blocks can use them. This file is then included by the scss files. There is no good dependency here, because there is no file for the export of my theme to depend on right now, just occasionally I have to run it.
|
||||
|
||||
#+begin_note
|
||||
I keep this particular target around for playing with, but right now it doesn't actually work. Just because even though I load and enable =yoshi-theme= it doesn't appear to apply the theme in a batch session. Seems understandable because no UI actually gets loaded, but that does mean that it can't figure out which faces it sets and it just outputs the colors for the default theme.
|
||||
#+end_note
|
||||
|
||||
#+begin_src makefile-gmake
|
||||
theme: ## Generate the theme CSS
|
||||
HOME=".emacs-local" emacs --quick --batch --load htmlize --load ox-html \
|
||||
-eval "(setq org-html-htmlize-output-type 'css)" \
|
||||
-funcall org-html-htmlize-generate-css \
|
||||
-load yoshi-theme \
|
||||
-eval "(enable-theme 'yoshi)" \
|
||||
-load make-mode \
|
||||
-eval "(kill-whole-line)" \
|
||||
-eval "(kill-whole-line)" \
|
||||
-eval "(goto-char (point-max))" \
|
||||
-eval "(forward-line -2)" \
|
||||
-eval "(kill-whole-line)" \
|
||||
-eval "(kill-whole-line)" \
|
||||
-eval "(css-mode)" \
|
||||
-eval "(indent-region (point-min) (point-max))" \
|
||||
-eval '(write-file "src/scss/include/_yoshi.scss")'
|
||||
#+end_src
|
||||
|
||||
Finally, as a precaution, I specify that all of the main targets are phony targets. This way if I ever introduce any file with the same name as these targets they will still build and not assume that because the file exists, everything is up-to-date.
|
||||
|
||||
#+begin_src makefile-gmake
|
||||
.PHONY: publish deploy html css help theme
|
||||
#+end_src
|
||||
|
||||
* Publishing project
|
||||
:PROPERTIES:
|
||||
:header-args:emacs-lisp: :tangle publish.el
|
||||
:END:
|
||||
|
||||
Before anything else I need to enable lexical binding.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
;;; -*- lexical-binding: t -*-
|
||||
#+end_src
|
||||
|
||||
I require =dockerfile-mode= because some of my posts include examples of docker files. And I use the latest =org= mode package. Some other packages must be loaded to make sure their features can be used by the export process as well.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(require 'dockerfile-mode)
|
||||
(require 'ob-dot)
|
||||
(require 'ox-publish)
|
||||
(require 'ox-rss)
|
||||
(require 'rainbow-delimiters)
|
||||
(require 'subr-x)
|
||||
#+end_src
|
||||
|
||||
After that I define a constant for the current root so that it's easy to refer to. I use =defconst= here instead of =defvar= for 2 reasons:
|
||||
|
||||
1. It's a constant, and shouldn't be changed during execution.
|
||||
2. Re-evaluating a =defvar= doesn't do anything[fn:1], but re-evaluating a =defconst= updates the value.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(defconst publish-root
|
||||
(file-name-directory
|
||||
(or load-file-name
|
||||
(buffer-file-name)))
|
||||
"The directory where ‘oni-org’ was loaded from.")
|
||||
#+end_src
|
||||
|
||||
Keep the timestamp cache (used to determine which pages need to be cleaned up) local. This prevents publishing from relying on global state on my PC and makes it easy to reset everything.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(setq org-publish-timestamp-directory
|
||||
(expand-file-name ".org-timestamps/" publish-root))
|
||||
#+end_src
|
||||
|
||||
I add the =rainbow-delimiters-mode= to the =prog-mode-hook= so that delimiters are highlighted like they are in my usual Emacs sessions.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(add-hook 'prog-mode-hook 'rainbow-delimiters-mode)
|
||||
#+end_src
|
||||
|
||||
This requires that I have the =rainbow-delimiters= package installed.
|
||||
|
||||
#+begin_src emacs-lisp :exports none :tangle no :noweb-ref required-packages :noweb-sep " "
|
||||
rainbow-delimiters
|
||||
#+end_src
|
||||
|
||||
Anything that is marked with either the =noexport= or =draft= tag shouldn't be exported.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(setq org-export-exclude-tags '("noexport" "draft"))
|
||||
#+end_src
|
||||
|
||||
Don't ask for my confirmation whether babel blocks should be evaluated. If I've added them I want them to run.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(setq org-confirm-babel-evaluate nil)
|
||||
#+end_src
|
||||
|
||||
Reset the org-mode style, I don't want the default style interfering with my own.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(setq org-html-head-include-default-style nil)
|
||||
#+end_src
|
||||
|
||||
Just add CSS class names to code blocks, I'll make sure that the CSS selectors exist.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(setq org-html-htmlize-output-type 'css)
|
||||
#+end_src
|
||||
|
||||
Use HTML5 and all its fancy elements.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(setq org-html-html5-fancy t)
|
||||
(setq org-html-doctype "html5")
|
||||
#+end_src
|
||||
|
||||
Disable VC. When I run this from a ~guix shell --pure~ I don't have git available and VC starts looking for it and throwing errors all over the place.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(setq vc-handled-backends nil)
|
||||
#+end_src
|
||||
|
||||
** Projects
|
||||
|
||||
Here come the actual projects.
|
||||
|
||||
#+begin_src emacs-lisp :noweb no-export :noweb-prefix no
|
||||
(setq org-publish-project-alist `(<<projects>>))
|
||||
#+end_src
|
||||
|
||||
*** Pages
|
||||
|
||||
The pages project is the main publishing project. It exports all of the =.org= files except for =README.org= and anything found in the =posts/= directory. The =README.org= is only relevant when you're looking at the source code for this website and =posts/= has its own project.
|
||||
|
||||
It also sets:
|
||||
|
||||
- =:html-head= :: Loads the stylesheet into the HTML head.
|
||||
- =:html-preamble-format= :: Adds a list of other sections of my website.
|
||||
- =:html-postamble-format= :: Adds a link to my Mastodon account in the postamble, for verification purposes.
|
||||
- =:html-home/up-format= :: Fixes the whitespace in the home/up links at the top of the page.
|
||||
|
||||
#+begin_src emacs-lisp :tangle no :noweb-ref projects
|
||||
("pages"
|
||||
:base-directory "."
|
||||
:base-extension "org"
|
||||
:publishing-directory "public/"
|
||||
:recursive t
|
||||
:exclude ,(rx string-start
|
||||
(or "posts/"
|
||||
(and "README.org" string-end)))
|
||||
:publishing-function org-html-publish-to-html
|
||||
:html-head "<link rel=\"stylesheet\" href=\"/assets/css/main.css\" type=\"text/css\"/>"
|
||||
:html-preamble t
|
||||
:html-preamble-format (("en" ,(string-join (list "<ul class=\"main-navigation\">"
|
||||
"<li><a href=\"//code.ryuslash.org\">code</a></li>"
|
||||
"<li><a href=\"//laminar.ryuslash.org\">builds</a></li>"
|
||||
"<li><a href=\"//blog.ryuslash.org\">blog</a></li>"
|
||||
"</ul>"))))
|
||||
:html-postamble t
|
||||
:html-postamble-format (("en" "<p class=\"social social-mastodon\">Find me on <a href=\"https://fosstodon.org/@ryuslash\" rel=\"me\">Mastodon</a></p>
|
||||
<p class=\"date\">Date: %C</p>
|
||||
<p class=\"creator\">%c</p>"))
|
||||
:html-home/up-format "<div id=\"org-div-home-and-up\"><a accesskey=\"h\" href=\"%s\">UP</a> | <a accesskey=\"H\" href=\"%s\">HOME</a>\n</div>")
|
||||
#+end_src
|
||||
|
||||
*** Posts
|
||||
|
||||
The posts project is an experiment right now to see if I can find a way I'm happy with to actually publish my blog through plain old org files. This goes together with the [[RSS]] project. This also calculates a rough estimate of how long the reading time for the page will be and adds it to the preamble of each post.
|
||||
|
||||
First I need a few of functions. Calculate how many minutes it would take to read the given buffer. I don't remember where I got this particular algorithm from, and I haven't tried out how accurate it is, but my experience with other websites doing this is that it's generally not very accurate anyway.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(defun publish-calculate-reading-time (buffer)
|
||||
"Calculate the amount of minutes it would take to read the contents of BUFFER."
|
||||
(with-current-buffer buffer
|
||||
(max 1 (/ (count-words (point-min) (point-max)) 228))))
|
||||
#+end_src
|
||||
|
||||
I decided to split the calculations and formatting functions, so here is just a simple function that format the given reading time.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(defun publish-format-reading-time (time)
|
||||
"Return a string describing TIME."
|
||||
(format "Reading time: %d minute%s"
|
||||
time
|
||||
(if (= time 1) "" "s")))
|
||||
#+end_src
|
||||
|
||||
And then I need a function to actually generate the preamble. This function ignores the =index.org= file, because that is not a page to read but just and index of my blog posts.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(defun publish-generate-post-preamble (_project)
|
||||
"Generate the preamble for a post with the estimated reading time."
|
||||
(unless (string= (file-name-nondirectory (buffer-file-name)) "index.org")
|
||||
(publish-format-reading-time
|
||||
(publish-calculate-reading-time (current-buffer)))))
|
||||
#+end_src
|
||||
|
||||
Finally the actual project. It publishes files inside the =posts/= directory.
|
||||
|
||||
#+begin_src emacs-lisp :tangle no :noweb-ref projects
|
||||
("posts"
|
||||
:base-directory "posts/"
|
||||
:base-extension "org"
|
||||
:publishing-directory "public/posts/"
|
||||
:recursive t
|
||||
:publishing-function org-html-publish-to-html
|
||||
:html-head "<link rel=\"stylesheet\" href=\"/assets/css/main.css\" type=\"text/css\"/>" ; (ref:html-head)
|
||||
:html-preamble publish-generate-post-preamble) ; (ref:html-preamble)
|
||||
#+end_src
|
||||
|
||||
At [[(html-head)]] I again load the CSS style sheet for this site. [[(html-preamble)]] calculates the page's reading time and puts it on the page.
|
||||
|
||||
*** RSS
|
||||
|
||||
The =rss= project works together with the =posts= project. This project very specifically generates and targets a specific file and exports it as an RSS feed. For this I require the =ox-rss= package.
|
||||
|
||||
#+begin_src emacs-lisp :exports none :tangle no :noweb-ref required-packages :noweb-sep " "
|
||||
ox-rss
|
||||
#+end_src
|
||||
|
||||
Before I can define my project I still need to have some helper functions. The first I'll make is the =publish-empty-time=. It's just a dumb little function that takes the current time and subtracts it with itself. This is used as an empty value, so that if compared to another time it'll always be less than the other time.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(defun publish-empty-time ()
|
||||
"Get an empty time value."
|
||||
(let ((current-time (current-time)))
|
||||
(time-subtract current-time current-time)))
|
||||
#+end_src
|
||||
|
||||
Next up is =publish-get-latest-modified-time= which takes a list of files and finds the latest time at which any of the files were modified.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(defun publish-get-latest-modified-time (files)
|
||||
"Get the latest modification time of any file from the list FILES."
|
||||
(car
|
||||
(last
|
||||
(sort (mapcar #'org-publish-cache-mtime-of-src files) #'time-less-p))))
|
||||
#+end_src
|
||||
|
||||
I don't know why exactly, but there is a =time-less-p= and a =time-equal-p=, but no =time-greater-p= or any other. Just for clarity I decided to implement ~publish-time>=~ which is just the complement of =time-less-p= (because if the time is not less, it has to be either greater than or equal to).
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(defun publish-time>= (a b)
|
||||
"Check if time A is greater than or equal to time B."
|
||||
(not (time-less-p a b)))
|
||||
#+end_src
|
||||
|
||||
Now a bigger function with a bit more meat to it. This function opens an org file and tries to get some information out of it to construct a headline from it with a link to the actual post. This is used to generate the index of all of the posts. It goes through the file trying to find any headings tagged with the tag =summary= and extracts the contents from the first one. This gives us something to put on the index so it's not just a plain list with links.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(defun publish-extract-summary-from-file (file props)
|
||||
"Extract a summary from FILE.
|
||||
PROPS is used as an aid in getting the right information from the file."
|
||||
(format "* %s\n:PROPERTIES:\n:CUSTOM_ID: %s\n:PUBDATE: %s\n:RSS_PERMALINK: %s\n:END:\n\n%s\n\n[[file:%s][Read More]]\n\n"
|
||||
(car (org-publish-find-property file :title props))
|
||||
(file-name-nondirectory file)
|
||||
(format-time-string "[%Y-%m-%d %a %H:%M]" (org-timestamp-to-time (car (org-publish-find-property file :date props))))
|
||||
(file-name-nondirectory file)
|
||||
(car (org-map-entries (lambda () (let ((element-data (cadr (org-element-at-point))))
|
||||
(buffer-substring-no-properties
|
||||
(map-elt element-data :contents-begin)
|
||||
(map-elt element-data :contents-end))))
|
||||
"summary"
|
||||
(list file)))
|
||||
(file-name-nondirectory file)))
|
||||
#+end_src
|
||||
|
||||
Finally the last function mushes it all together and actually generates an =index.org= file. I just looks through the =posts/= directory and finds any files that start with a date. It compares the last modification time of the =index.org= and that of the latest modified post, and skips generating if the =index.org= is newer. Then it takes the 30 latest posts and extracts the necessary information from them and writes it all into =index.org=.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(require 'dash)
|
||||
|
||||
(defun publish-generate-index (props)
|
||||
"Generate an index from my posts.
|
||||
Argument PROPS
|
||||
."
|
||||
(let* ((index-file (expand-file-name "posts/index.org"))
|
||||
(index-generated-time (or (and (file-exists-p index-file)
|
||||
(org-publish-cache-mtime-of-src index-file))
|
||||
(publish-empty-time)))
|
||||
(files (directory-files "posts/" t (rx bos (= 8 digit) "-" (= 4 digit) "-" (one-or-more nonl) (not "~") eos)))
|
||||
(latest-modification-time (publish-get-latest-modified-time files)))
|
||||
(if (publish-time>= index-generated-time latest-modification-time)
|
||||
(message "Not generating index...")
|
||||
(progn
|
||||
(message "Generating index...")
|
||||
(with-temp-buffer
|
||||
(insert "#+title: ryuslash's blog\n")
|
||||
(insert "#+options: num:nil toc:nil\n")
|
||||
(insert "#+html_link_up: /\n")
|
||||
(insert "#+html_link_home: /\n")
|
||||
(insert "\n")
|
||||
(apply 'insert
|
||||
(mapcar (lambda (file) (publish-extract-summary-from-file file props))
|
||||
(-take 30 (reverse files))))
|
||||
(write-file "posts/index.org"))))))
|
||||
#+end_src
|
||||
|
||||
With that all out of the way I can finally assemble my project.
|
||||
|
||||
#+begin_src emacs-lisp :tangle no :noweb-ref projects
|
||||
("rss"
|
||||
:base-directory "posts/"
|
||||
:base-extension "org"
|
||||
:rss-extension "xml"
|
||||
:preparation-function publish-generate-index ; (ref:preparation-function)
|
||||
:publishing-directory "public/posts/"
|
||||
:publishing-function (org-rss-publish-to-rss) ; (ref:publishing-function)
|
||||
:html-link-home "https://ryuslash.org/posts/"
|
||||
:html-link-use-abs-url t
|
||||
:section-numbers nil
|
||||
:exclude ".*" ; (ref:exclude)
|
||||
:include ("index.org")
|
||||
:table-of-contents nil)
|
||||
#+end_src
|
||||
|
||||
The [[(preparation-function)]] uses my =publish-generate-index= function to create the =index.org= before trying to turn it into an RSS feed. The [[(publishing-function)]] exports the org file to an RSS feed, this is from the =ox-rss= package. And [[(exclude)]] excludes everything, and then we include the =index.org= again so that it only tries to turn the =index.org= into an RSS feed, not anything else.
|
||||
|
||||
*** Assets
|
||||
|
||||
This is a simple project that just goes around and copies over any asset files (=.svg=, =.jpg=, etc.) from the source directory into the publish directory.
|
||||
|
||||
#+begin_src emacs-lisp :tangle no :noweb-ref projects
|
||||
("assets"
|
||||
:base-directory "."
|
||||
:recursive t
|
||||
:exclude "^public/"
|
||||
:base-extension "svg\\|png\\|jpg\\|ico"
|
||||
:publishing-function org-publish-attachment
|
||||
:publishing-directory "public/")
|
||||
#+end_src
|
||||
|
||||
*** All
|
||||
|
||||
This is a convenience project so that I can publish everything all at once. It also establishes the right order in which to do so. Really the only order is that [[RSS]] has to go before [[Posts]] because the RSS preparation function needs to happen before the =index.org= it generates can be included in the Posts export.
|
||||
|
||||
#+begin_src emacs-lisp :tangle no :noweb-ref projects
|
||||
("all" :components ("pages" "rss" "posts" "assets"))
|
||||
#+end_src
|
||||
|
||||
* Footnotes
|
||||
|
||||
[fn:1] You have to evaluate it in a special way. Either with {{{kbd(C-M-x)}}} or {{{kbd(C-u C-x C-e)}}}.
|
||||
# Local Variables:
|
||||
# org-src-preserve-indentation: t
|
||||
# End:
|
15
manifest.scm
15
manifest.scm
|
@ -1,4 +1,15 @@
|
|||
(packages->manifest
|
||||
(list
|
||||
(specification->package "emacs-eldev")
|
||||
(specification->package "emacs-htmlize")))
|
||||
(specification->package "coreutils")
|
||||
(specification->package "make")
|
||||
(specification->package "emacs")
|
||||
(specification->package "emacs-org")
|
||||
(specification->package "emacs-dash")
|
||||
(specification->package "emacs-htmlize")
|
||||
(specification->package "emacs-dockerfile-mode")
|
||||
(specification->package "emacs-rainbow-delimiters")
|
||||
(specification->package "emacs-ox-rss")
|
||||
(specification->package "openssh")
|
||||
(specification->package "rsync")
|
||||
(specification->package "ruby-sass")
|
||||
(specification->package "php")))
|
||||
|
|
45
posts/20231107-til-i-can-use-elfeed-search-face-alist.org
Normal file
45
posts/20231107-til-i-can-use-elfeed-search-face-alist.org
Normal file
|
@ -0,0 +1,45 @@
|
|||
#+title: TIL: I can use elfeed-search-face-alist to highlight certain headlines in Elfeed
|
||||
#+tags: emacs, elfeed, til
|
||||
#+comments: on
|
||||
#+status: publish
|
||||
#+DATE: Thu, 07 Dec 2023 22:45:51 GMT
|
||||
#+UPDATE_URL: /admin/modify-post/2023%252f12%252f07%252ftil-i-can-use-elfeed-search-face-alist-to-highlight-certain-headlines-in-elfeed
|
||||
|
||||
I rediscovered that I can use =elfeed-search-face-alist= to customize how headlines are displayed in Elfeed. I had read it before [[https://nullprogram.com/blog/2015/12/03/#custom-entry-colors][on Chris Wellons' blog]], but I didn't have a use for it then.
|
||||
|
||||
With =elfeed-search-face-alist= I can define a face to be used for an article with a specific tag. I added the [[https://blabbermouth.net/][Blabbermouth]] RSS feed to my feeds.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(setq elfeed-feeds '(("https://blabbermouth.net/feed" music)))
|
||||
#+end_src
|
||||
|
||||
And created 2 taggers. One tags reviews, because those always have =/reviews/= in the url. The other has a list of bands that I'm especially interested in.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(defvar oni-elfeed-blabbermouth-review-tagger
|
||||
(elfeed-make-tagger :feed-url (rx "blabbermouth.net")
|
||||
:entry-link (rx "/reviews/")
|
||||
:add 'review)
|
||||
"Tagger that marks any reviews from Blabbermouth.")
|
||||
|
||||
(defvar oni-elfeed-blabbermouth-favourite-tagger
|
||||
(elfeed-make-tagger :feed-url (rx "blabbermouth.net")
|
||||
:entry-title (rx (or "SLIPKNOT"
|
||||
(seq "DREAM" whitespace "THEATER")
|
||||
;; And so on...
|
||||
))
|
||||
:add 'favourite)
|
||||
"Tagger that highlights specific bands from Blabbermouth.")
|
||||
|
||||
(add-hook 'elfeed-new-entry-hook oni-elfeed-blabbermouth-favourite-tagger)
|
||||
(add-hook 'elfeed-new-entry-hook oni-elfeed-blabbermouth-review-tagger)
|
||||
#+end_src
|
||||
|
||||
And then I can just set up the feeds:
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(add-to-list 'elfeed-search-face-alist '(review :slant italic) t)
|
||||
(add-to-list 'elfeed-search-face-alist '(favourite :foreground "#f17272") t)
|
||||
#+end_src
|
||||
|
||||
As long as the face definitions don't conflict a headline's face would be the combination of all that apply. For example, by default unread headlines are bold, so unread favourite messages would be bold and somewhat reddish.
|
16
posts/20240205-til-how-to-escape-backtick-in-markdown.org
Normal file
16
posts/20240205-til-how-to-escape-backtick-in-markdown.org
Normal file
|
@ -0,0 +1,16 @@
|
|||
#+title: TIL: How to Escape a Backtick in Markdown
|
||||
#+tags: til
|
||||
#+comments: on
|
||||
#+status: publish
|
||||
#+DATE: Tue, 06 Feb 2024 06:47:35 GMT
|
||||
#+UPDATE_URL: /admin/modify-post/2024%252f02%252f06%252ftil-how-to-escape-a-backtick-in-markdown
|
||||
|
||||
I needed to send a bit of inline code in some Markdown today and it needed to have a =‘= in it. I realized I hadn't done that before. I write code with =‘= in it somewhat frequently because Lisp uses it for quasi-quoting, but I always write everything in org syntax where you can use either ~=~ or =~=. But in Markdown if you need to have a =‘= in your inline code you need to wrap your inline code with more =‘= characters.
|
||||
|
||||
#+begin_src markdown
|
||||
`This is some inline code`
|
||||
``This is some inline code with a ` (backtick) in it``
|
||||
```This is some inline code with `` (two backticks) in it```
|
||||
#+end_src
|
||||
|
||||
It looks like the sky might be the limit.
|
11
projects.org
11
projects.org
|
@ -3,11 +3,18 @@
|
|||
|
||||
Most of my projects end up being for [[file:emacs.org][Emacs]] for some reason. I sometimes have trouble finding the energy to maintain these projects, but I'm trying my best to get back to it.
|
||||
|
||||
* tekuti.el
|
||||
|
||||
- Source :: https://code.ryuslash.org/ryuslash/tekuti-el
|
||||
|
||||
|
||||
A small package for [[file:emacs.org][Emacs]] that allows me to quickly post to my tekuti [[https://blog.ryuslash.org][blog]].
|
||||
|
||||
* Avandu
|
||||
|
||||
- Source :: https://github.com/ryuslash/avandu
|
||||
|
||||
A package for [[file:emacs.org
|
||||
][Emacs]] that connects to [[https://tt-rss.org/][Tiny Tiny RSS]]. I really wanted something that didn't look like all the other RSS readers, meaning it didn't just have a plain table showing all the articles.
|
||||
|
||||
A package for [[file:emacs.org][Emacs]] that connects to [[https://tt-rss.org/][Tiny Tiny RSS]]. I really wanted something that didn't look like all the other RSS readers, meaning it didn't just have a plain table showing all the articles.
|
||||
|
||||
I don't use Tiny Tiny RSS anymore, but if problems come up I still try to fix them. For the (perhaps) 2 people that use it.
|
||||
|
|
180
publish.el
180
publish.el
|
@ -1,180 +0,0 @@
|
|||
;;; publish.el --- Publishing configuration for ryuslash.org -*- lexical-binding: t; -*-
|
||||
|
||||
;; Copyright (C) 2020 Tom Willemse
|
||||
|
||||
;; Author: Tom Willemse <tom@ryuslash.org>
|
||||
;; Keywords:
|
||||
;; Version: 1
|
||||
;; Package-Requires: (dockerfile-mode htmlize org org-contrib rainbow-delimiters ox-rss)
|
||||
|
||||
;; This program is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'dockerfile-mode)
|
||||
(require 'ob-dot)
|
||||
(require 'ox-publish)
|
||||
(require 'ox-rss)
|
||||
(require 'rainbow-delimiters)
|
||||
(require 'subr-x)
|
||||
|
||||
(defconst publish-root
|
||||
(file-name-directory
|
||||
(or load-file-name
|
||||
(buffer-file-name)))
|
||||
"The directory where ‘oni-org’ was loaded from.")
|
||||
|
||||
(add-hook 'prog-mode-hook 'rainbow-delimiters-mode)
|
||||
|
||||
(defun publish-calculate-reading-time (buffer)
|
||||
"Calculate the amount of minutes it would take to read the contents of BUFFER."
|
||||
(with-current-buffer buffer
|
||||
(max 1 (/ (count-words (point-min) (point-max)) 228))))
|
||||
|
||||
(defun publish-format-reading-time (time)
|
||||
"Return a string describing TIME."
|
||||
(format "%d minute%s"
|
||||
time
|
||||
(if (= time 1) "" "s")))
|
||||
|
||||
(defun publish-extract-summary-from-file (file props)
|
||||
"Extract a summary from FILE.
|
||||
PROPS is used as an aid in getting the right information from the file."
|
||||
(format "* %s\n:PROPERTIES:\n:CUSTOM_ID: %s\n:PUBDATE: %s\n:RSS_PERMALINK: %s\n:END:\n\n%s\n\n[[file:%s][Read More]]\n\n"
|
||||
(car (org-publish-find-property file :title props))
|
||||
(file-name-nondirectory file)
|
||||
(format-time-string "[%Y-%m-%d %a %H:%M]" (org-timestamp-to-time (car (org-publish-find-property file :date props))))
|
||||
(file-name-nondirectory file)
|
||||
(car (org-map-entries (lambda () (let ((element-data (cadr (org-element-at-point))))
|
||||
(buffer-substring-no-properties
|
||||
(map-elt element-data :contents-begin)
|
||||
(map-elt element-data :contents-end))))
|
||||
"summary"
|
||||
(list file)))
|
||||
(file-name-nondirectory file)))
|
||||
|
||||
(defun publish-get-latest-modified-time (files)
|
||||
"Get the latest modification time of any file from the list FILES."
|
||||
(car
|
||||
(last
|
||||
(sort (mapcar #'org-publish-cache-mtime-of-src files) #'time-less-p))))
|
||||
|
||||
(defun publish-time>= (a b)
|
||||
"Check if time A is greater than or equal to time B."
|
||||
(not (time-less-p a b)))
|
||||
|
||||
(defun publish-empty-time ()
|
||||
"Get an empty time value."
|
||||
(let ((current-time (current-time)))
|
||||
(time-subtract current-time current-time)))
|
||||
|
||||
(defun publish-generate-index (props)
|
||||
"Generate an index from my posts.
|
||||
Argument PROPS
|
||||
."
|
||||
(let* ((index-file (expand-file-name "posts/index.org"))
|
||||
(index-generated-time (or (and (file-exists-p index-file)
|
||||
(org-publish-cache-mtime-of-src index-file))
|
||||
(publish-empty-time)))
|
||||
(files (directory-files "posts/" t (rx bos (= 8 digit) "-" (= 4 digit) "-" (one-or-more nonl) (not "~") eos)))
|
||||
(latest-modification-time (publish-get-latest-modified-time files)))
|
||||
(if (publish-time>= index-generated-time latest-modification-time)
|
||||
(message "Not generating index...")
|
||||
(progn
|
||||
(message "Generating index...")
|
||||
(with-temp-buffer
|
||||
(insert "#+title: ryuslash's blog\n")
|
||||
(insert "#+options: num:nil\n")
|
||||
(insert "\n")
|
||||
(apply 'insert
|
||||
(mapcar (lambda (file) (publish-extract-summary-from-file file props))
|
||||
(take 30 (reverse files))))
|
||||
(write-file "posts/index.org"))))))
|
||||
|
||||
(setq org-export-exclude-tags '("noexport" "draft"))
|
||||
|
||||
(setq org-confirm-babel-evaluate nil)
|
||||
|
||||
(setq org-html-head-include-default-style nil)
|
||||
|
||||
(setq org-html-htmlize-output-type 'css)
|
||||
|
||||
(setq org-publish-timestamp-directory
|
||||
(concat default-directory "/.org-timestamps/"))
|
||||
|
||||
(setq org-html-html5-fancy t)
|
||||
|
||||
(setq org-html-doctype "html5")
|
||||
|
||||
(setq org-publish-project-alist
|
||||
`(("index"
|
||||
:base-directory "."
|
||||
:base-extension "org"
|
||||
:publishing-directory "public/"
|
||||
:recursive t
|
||||
:exclude ,(rx string-start
|
||||
(or "posts/"
|
||||
(and "README.org" string-end)))
|
||||
:publishing-function org-html-publish-to-html
|
||||
:html-head "<link rel=\"stylesheet\" href=\"/assets/css/main.css\" type=\"text/css\"/>"
|
||||
:html-postamble t
|
||||
:html-postamble-format (("en" "<p class=\"social social-mastodon\">Find me on <a href=\"https://fosstodon.org/@ryuslash\" rel=\"me\">Mastodon</a></p>
|
||||
<p class=\"date\">Date: %C</p>
|
||||
<p class=\"creator\">%c</p>")))
|
||||
("posts"
|
||||
:base-directory "posts/"
|
||||
:base-extension "org"
|
||||
:publishing-directory "public/posts/"
|
||||
:recursive t
|
||||
:publishing-function org-html-publish-to-html
|
||||
:html-head "<link rel=\"stylesheet\" href=\"/assets/css/main.css\" type=\"text/css\"/>"
|
||||
:html-preamble (lambda (project)
|
||||
(let ((buffer (find-file-noselect (buffer-file-name))))
|
||||
(unless (member (car (org-publish-find-property (buffer-file-name) :title project))
|
||||
'("ryuslash.org" "ryuslash's blog"))
|
||||
(publish-format-reading-time
|
||||
(publish-calculate-reading-time buffer))))))
|
||||
("rss"
|
||||
:base-directory "posts/"
|
||||
:base-extension "org"
|
||||
:rss-extension "xml"
|
||||
:preparation-function publish-generate-index
|
||||
:publishing-directory "public/posts/"
|
||||
:publishing-function (org-rss-publish-to-rss)
|
||||
:html-link-home "https://ryuslash.org/posts/"
|
||||
:html-link-use-abs-url t
|
||||
:section-numbers nil
|
||||
:exclude ".*"
|
||||
:include ("index.org")
|
||||
:table-of-contents nil)
|
||||
("assets"
|
||||
:base-directory "."
|
||||
:recursive t
|
||||
:exclude "^public/"
|
||||
:base-extension "svg\\|png\\|jpg"
|
||||
:publishing-function org-publish-attachment
|
||||
:publishing-directory "public/")
|
||||
("all" :components ("index" "rss" "posts" "assets"))))
|
||||
|
||||
(defvar publish-feed-url-format
|
||||
;"https://gitlab.com/ryuslash/ryuslash.org/-/commits/master/%s?feed_token=Rf8otgpS8YEiYakJN4NR&format=atom"
|
||||
"https://code.ryuslash.org/new-ryuslash.org/atom/%s?h=master"
|
||||
"Format string for the URL to the page’s atom feed.")
|
||||
|
||||
(provide 'publish)
|
||||
;;; publish.el ends here
|
|
@ -1,9 +0,0 @@
|
|||
@dark-background: #222424;
|
||||
@dark-background-highlight: #3f4242;
|
||||
@dark-foreground: #bfbfbf;
|
||||
@dark-primary: #ff9800;
|
||||
|
||||
@light-background: #f2f2f2;
|
||||
@light-background-highlight: #d9d9d9;
|
||||
@light-foreground: #111414;
|
||||
@light-primary: #1d44b8;
|
|
@ -14,12 +14,12 @@ a.button {
|
|||
display: inline-block;
|
||||
padding: 0 5px;
|
||||
background-color: #1e2f47;
|
||||
color: @dark-foreground;
|
||||
color: $dark-foreground;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
background-color: #71a3f0;
|
||||
color: @dark-background;
|
||||
color: $dark-background;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,10 +29,10 @@ a.button {
|
|||
|
||||
.highlight {
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background-color: @dark-background !important;
|
||||
background-color: $dark-background !important;
|
||||
}
|
||||
@media (prefers-color-scheme: light) {
|
||||
background-color: @light-background !important;
|
||||
background-color: $light-background !important;
|
||||
}
|
||||
|
||||
.nv {
|
||||
|
@ -62,7 +62,7 @@ a.button {
|
|||
}
|
||||
|
||||
.main {
|
||||
&:extend(.title-header);
|
||||
@extend .title-header;
|
||||
|
||||
display: block;
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ a.button {
|
|||
}
|
||||
|
||||
.pager {
|
||||
.listless-list();
|
||||
@include listless-list;
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
@ -108,7 +108,7 @@ a.button {
|
|||
}
|
||||
|
||||
.sub {
|
||||
&:extend(.subtitle);
|
||||
@extend .subtitle;
|
||||
|
||||
display: block;
|
||||
}
|
||||
|
@ -121,7 +121,7 @@ a.button {
|
|||
margin: 0;
|
||||
|
||||
&.active {
|
||||
background-color: @dark-background-highlight;
|
||||
background-color: $dark-background-highlight;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,7 +132,7 @@ a.button {
|
|||
}
|
||||
|
||||
#cgit {
|
||||
&:extend(.content-wrapper);
|
||||
@extend .content-wrapper;
|
||||
|
||||
position: relative;
|
||||
}
|
9
src/scss/include/_colors.scss
Normal file
9
src/scss/include/_colors.scss
Normal file
|
@ -0,0 +1,9 @@
|
|||
$dark-background: #222424;
|
||||
$dark-background-highlight: #3f4242;
|
||||
$dark-foreground: #bfbfbf;
|
||||
$dark-primary: #ff9800;
|
||||
|
||||
$light-background: #f2f2f2;
|
||||
$light-background-highlight: #d9d9d9;
|
||||
$light-foreground: #111414;
|
||||
$light-primary: #1d44b8;
|
|
@ -4,7 +4,7 @@ html {
|
|||
max-width: 70ch;
|
||||
padding: 0.2em 1em;
|
||||
margin: auto;
|
||||
line-height: 1.75;
|
||||
line-height: 1.2;
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
|
@ -12,18 +12,18 @@ a {
|
|||
text-decoration: underline;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
color: @dark-primary;
|
||||
color: $dark-primary;
|
||||
|
||||
&:visited {
|
||||
color: darken(@dark-primary, 10%);
|
||||
color: darken($dark-primary, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
color: @light-primary;
|
||||
color: $light-primary;
|
||||
|
||||
&:visited {
|
||||
color: darken(@light-primary, 10%);
|
||||
color: darken($light-primary, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,13 +46,13 @@ body {
|
|||
margin-top: 0;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background-color: @dark-background;
|
||||
color: @dark-foreground;
|
||||
background-color: $dark-background;
|
||||
color: $dark-foreground;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
background-color: @light-background;
|
||||
color: @light-foreground;
|
||||
background-color: $light-background;
|
||||
color: $light-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,11 +74,11 @@ h2 {
|
|||
border-bottom-style: solid;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
border-bottom-color: @dark-foreground;
|
||||
border-bottom-color: $dark-foreground;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
border-bottom-color: @light-foreground;
|
||||
border-bottom-color: $light-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,4 +109,21 @@ pre {
|
|||
|
||||
p { margin: 0; }
|
||||
|
||||
p + p { margin: 1rem 0; }
|
||||
p + p { margin: 1rem 0 0 0; }
|
||||
|
||||
.footdef {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
> p, > sup {
|
||||
margin: 0;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.figure svg {
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
pre.src {
|
||||
overflow: auto;
|
||||
margin: 0.5rem 0;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
|
@ -16,11 +18,11 @@ pre.src {
|
|||
clear: both;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
border-top: 3px @dark-foreground dotted;
|
||||
border-top: 3px $dark-foreground dotted;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
border-top: 3px @light-foreground dotted;
|
||||
border-top: 3px $light-foreground dotted;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,7 +37,7 @@ pre.src {
|
|||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.listless-list {
|
||||
@mixin listless-list {
|
||||
list-style-type: none;
|
||||
|
||||
li {
|
||||
|
@ -81,9 +83,9 @@ pre.src {
|
|||
border-bottom-style: solid;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
border-bottom-color: @dark-foreground;
|
||||
border-bottom-color: $dark-foreground;
|
||||
}
|
||||
@media (prefers-color-scheme: light) {
|
||||
border-bottom-color: @light-foreground;
|
||||
border-bottom-color: $light-foreground;
|
||||
}
|
||||
}
|
|
@ -11,37 +11,40 @@ img.book-cover {
|
|||
}
|
||||
|
||||
.title {
|
||||
&:extend(.title-header);
|
||||
@extend .title-header;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
&:extend(.subtitle);
|
||||
@extend .subtitle;
|
||||
}
|
||||
|
||||
#avatar {
|
||||
float: left;
|
||||
padding: 0.5rem 1rem;
|
||||
padding: 0 0.5rem 1rem 0;
|
||||
}
|
||||
|
||||
#org-div-home-and-up {
|
||||
&:extend(.content-wrapper);
|
||||
@extend .content-wrapper;
|
||||
|
||||
text-align: right;
|
||||
font-size: 0.8rem;
|
||||
padding-right: 1rem;
|
||||
float: right;
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
#postamble {
|
||||
&:extend(.footer);
|
||||
&:extend(.content-wrapper);
|
||||
@extend .footer;
|
||||
@extend .content-wrapper;
|
||||
|
||||
p {
|
||||
&:extend(.footer-text);
|
||||
@extend .footer-text;
|
||||
}
|
||||
}
|
||||
|
||||
#content {
|
||||
&:extend(.content-wrapper);
|
||||
@extend .content-wrapper;
|
||||
}
|
||||
|
||||
figure {
|
||||
|
@ -49,16 +52,16 @@ figure {
|
|||
}
|
||||
|
||||
pre.src {
|
||||
@import (less) "yoshi.css";
|
||||
@import "include/yoshi.scss";
|
||||
|
||||
position: static;
|
||||
|
||||
&::before {
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background-color: @dark-background;
|
||||
background-color: $dark-background;
|
||||
}
|
||||
@media (prefers-color-scheme: light) {
|
||||
background-color: @light-background;
|
||||
background-color: $light-background;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -68,8 +71,20 @@ pre.src {
|
|||
.org-center { text-align: center; }
|
||||
|
||||
.main-navigation {
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
line-height: 1;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
display: inline;
|
||||
list-style: none;
|
||||
|
||||
&:not(:last-child)::after {
|
||||
content: " | ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.org-src-container span:target {
|
||||
background-color: #1b214a;
|
||||
}
|
|
@ -4,23 +4,23 @@
|
|||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
h2.storytitle {
|
||||
border-bottom: 3px dotted @dark-foreground;
|
||||
border-bottom: 3px dotted $dark-foreground;
|
||||
}
|
||||
|
||||
#menu {
|
||||
background-color: @dark-background;
|
||||
color: @dark-foreground;
|
||||
background-color: $dark-background;
|
||||
color: $dark-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
h2.storytitle {
|
||||
border-bottom: 3px dotted @light-foreground;
|
||||
border-bottom: 3px dotted $light-foreground;
|
||||
}
|
||||
|
||||
#menu {
|
||||
background-color: @light-background;
|
||||
color: @light-foreground;
|
||||
background-color: $light-background;
|
||||
color: $light-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,16 +56,6 @@ p.footpara {
|
|||
text-align: right;
|
||||
}
|
||||
|
||||
.footdef {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
|
||||
> p {
|
||||
margin: 0;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.org-src-container, .example {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
@ -75,13 +65,13 @@ p.footpara {
|
|||
}
|
||||
|
||||
#footer {
|
||||
&:extend(.footer);
|
||||
&:extend(.footer-text);
|
||||
@extend .footer;
|
||||
@extend .footer-text;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#header {
|
||||
&:extend(.title-header);
|
||||
@extend .title-header;
|
||||
}
|
||||
|
||||
#menu {
|
||||
|
@ -146,9 +136,9 @@ p.footpara {
|
|||
}
|
||||
|
||||
#navbar {
|
||||
&:extend(.subtitle);
|
||||
@extend .subtitle;
|
||||
}
|
||||
|
||||
#rap {
|
||||
&:extend(.content-wrapper);
|
||||
@extend .content-wrapper;
|
||||
}
|
Loading…
Reference in a new issue