-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbuild.el
More file actions
244 lines (221 loc) · 9.86 KB
/
build.el
File metadata and controls
244 lines (221 loc) · 9.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
;;; build.el --- Build system for the blog
;; This file contains the Emacs Lisp code for building the blog
;; It converts Org-mode files to HTML with proper templating
;;; Code:
;; Set up directories
;; Prefer explicit env vars, then CI workspace, then the directory of this file.
(setq blog-directory
(file-name-as-directory
(expand-file-name
(or (getenv "BLOG_DIR")
(getenv "GITHUB_WORKSPACE")
(and load-file-name (file-name-directory load-file-name))
default-directory))))
(setq blog-posts-directory (concat blog-directory "posts/"))
(setq blog-publish-directory (concat blog-directory "public/"))
(setq blog-static-directory (concat blog-directory "static/"))
(setq blog-templates-directory (concat blog-directory "templates/"))
;; HTML escaping function for security
(defun blog/html-escape (str)
"Escape HTML special characters to prevent XSS attacks."
(if (not str)
""
(unless (stringp str)
(setq str (format "%s" str)))
(with-temp-buffer
(insert str)
(goto-char (point-min))
(while (search-forward-regexp "[&<>\"']" nil t)
(replace-match
(pcase (match-string 0)
("&" "&")
("<" "<")
(">" ">")
("\"" """)
("'" "'"))
t t))
(buffer-string))))
;; Set user info (use environment variables for security)
(setq user-full-name (or (getenv "BLOG_AUTHOR") "bytenoob"))
(setq user-mail-address (or (getenv "BLOG_EMAIL") "noreply@example.com"))
(setq blog-title-raw (or (getenv "BLOG_TITLE") "Noob Notes"))
(setq blog-title (blog/html-escape blog-title-raw))
;; Load org and ox-publish
(require 'org)
(require 'ox-publish)
;; Try to load htmlize for syntax highlighting
(condition-case nil
(progn
;; Try to load from standard locations
(require 'htmlize nil t)
;; If not found, try to load from a local copy
(unless (featurep 'htmlize)
(let ((htmlize-file (expand-file-name "htmlize.el" blog-directory)))
(when (file-exists-p htmlize-file)
(load-file htmlize-file))))
;; Set htmlize output type if available
(when (featurep 'htmlize)
(setq org-html-htmlize-output-type 'css)
(setq org-html-htmlize-font-prefix "org-")
;; Try to load language modes for better syntax highlighting
;; Support multiple Emacs versions by finding any build directory
(when (getenv "HOME")
(let ((straight-dir (expand-file-name ".config/doom/.local/straight/" (getenv "HOME"))))
(when (file-directory-p straight-dir)
(dolist (build-dir (directory-files straight-dir t "^build"))
(when (file-directory-p build-dir)
(dolist (pkg '("go-mode" "rust-mode" "python-mode" "js2-mode"))
(let ((pkg-dir (expand-file-name pkg build-dir)))
(when (file-directory-p pkg-dir)
(add-to-list 'load-path pkg-dir)))))))))
;; Load language modes if available
(ignore-errors (require 'python-mode nil t))
(ignore-errors (require 'js2-mode nil t))
(ignore-errors (require 'js-mode nil t))
(ignore-errors (require 'go-mode nil t))
(ignore-errors (require 'rust-mode nil t))
(ignore-errors (require 'sql-mode nil t))
(ignore-errors (require 'sh-script nil t))
(ignore-errors (require 'css-mode nil t))
(ignore-errors (require 'json-mode nil t))
(ignore-errors (require 'yaml-mode nil t))
;; Set up file associations for proper mode detection
(add-to-list 'auto-mode-alist '("\\.go\\'" . go-mode))
(add-to-list 'auto-mode-alist '("\\.rs\\'" . rust-mode))
(add-to-list 'auto-mode-alist '("\\.py\\'" . python-mode))
(add-to-list 'auto-mode-alist '("\\.js\\'" . js-mode))
))
(error
;; If htmlize fails to load, continue without syntax highlighting
(message "Warning: htmlize not available, syntax highlighting disabled")))
;; Common HTML head content
(defun blog/html-head ()
(concat "<meta http-equiv=\"Content-Security-Policy\" content=\"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self';\">
<meta http-equiv=\"X-Frame-Options\" content=\"SAMEORIGIN\">
<meta http-equiv=\"X-Content-Type-Options\" content=\"nosniff\">
<meta http-equiv=\"Referrer-Policy\" content=\"strict-origin-when-cross-origin\">
<link rel=\"icon\" type=\"image/svg+xml\" href=\"/static/favicon.svg\">
<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">
<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>
<link href=\"https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&family=Source+Sans+3:wght@400;500;600&family=Source+Serif+4:opsz,wght@8..60,400;8..60,500;8..60,600&display=swap\" rel=\"stylesheet\">
<link rel=\"stylesheet\" type=\"text/css\" href=\"/static/css/blog.css?v=" (or (getenv "CSS_VERSION") "1") "\" />
<script src=\"/static/js/blog.js?v=" (or (getenv "JS_VERSION") "1") "\" defer></script>"))
;; Custom HTML preamble and postamble
(defun blog/preamble (info)
(concat
"<header class=\"site-header\">"
"<div class=\"container\">"
"<h1 class=\"site-title\"><a href=\"/\">" blog-title "</a></h1>"
"<nav class=\"site-nav\">"
"<a href=\"/\">Home</a>"
"<a href=\"/about.html\">About</a>"
"<div class=\"lang-switcher\">"
"<button class=\"lang-btn active\" data-lang=\"en\">EN</button>"
"<span class=\"lang-divider\">|</span>"
"<button class=\"lang-btn\" data-lang=\"cn\">中文</button>"
"</div>"
"</nav>"
"</div>"
"</header>"))
(defun blog/postamble (info)
(concat
"<footer class=\"site-footer\">"
"<div class=\"container\">"
"<p>© " (format-time-string "%Y")
" - Built with Emacs " emacs-version
" & Org-mode " (org-version) "</p>"
"</div>"
"</footer>"))
;; Function to check if a file is a draft
(defun blog/is-draft-p (file)
"Check if FILE has #+DRAFT: true property."
(with-temp-buffer
(insert-file-contents file)
(goto-char (point-min))
(re-search-forward "^#\\+DRAFT:\\s-*\\(true\\|t\\|yes\\)" nil t)))
;; Function to check if file needs rebuilding
(defun blog/needs-rebuild-p (org-file html-file)
"Check if ORG-FILE needs to be rebuilt by comparing with HTML-FILE."
(or (not (file-exists-p html-file))
(file-newer-than-file-p org-file html-file)
(not (string= (getenv "INCREMENTAL_BUILD") "true"))))
;; Custom publishing function that skips drafts and unchanged files
(defun blog/publish-to-html (plist filename pub-dir)
"Publish an org file to HTML, but skip if it's a draft or unchanged."
(unless (blog/is-draft-p filename)
(let* ((html-file (concat pub-dir
(file-name-sans-extension
(file-name-nondirectory filename))
".html")))
(if (blog/needs-rebuild-p filename html-file)
(progn
(message "Building: %s" filename)
(org-html-publish-to-html plist filename pub-dir))
(message "Skipping unchanged: %s" filename)))))
;; Custom sitemap function to exclude author and draft posts
(defun blog/sitemap-function (title list)
"Generate sitemap as an Org file without author metadata and draft posts."
(concat "#+TITLE: " blog-title-raw "\n"
"#+AUTHOR:\n"
"#+OPTIONS: author:nil toc:nil num:nil h:0\n\n"
(org-list-to-org list)))
;; Publishing configuration
(setq org-publish-project-alist
`(("blog-posts"
:base-directory ,blog-posts-directory
:base-extension "org"
:publishing-directory ,blog-publish-directory
:recursive t
:publishing-function blog/publish-to-html
:headline-levels 4
:section-numbers nil
:with-toc t
:with-author t
:with-date t
:html-head ,(blog/html-head)
:html-preamble blog/preamble
:html-postamble blog/postamble
:html-head-include-default-style nil
:html-head-include-scripts nil
:auto-sitemap t
:sitemap-filename "index.org"
:sitemap-title "Posts"
:sitemap-sort-files anti-chronologically
:sitemap-style list
:sitemap-format-entry (lambda (entry style project)
(format "[[file:%s][%s]]"
entry
(org-publish-find-title entry project)))
:sitemap-function blog/sitemap-function
:exclude "404\\.org\\|about\\.org\\|-cn\\.org\\|index-cn\\.org")
("blog-pages"
:base-directory ,blog-posts-directory
:base-extension "org"
:publishing-directory ,blog-publish-directory
:recursive nil
:publishing-function blog/publish-to-html
:headline-levels 4
:section-numbers nil
:with-toc t
:with-author t
:with-date t
:html-head ,(blog/html-head)
:html-preamble blog/preamble
:html-postamble blog/postamble
:html-head-include-default-style nil
:html-head-include-scripts nil
:include ("about.org" "index-cn.org"))
("blog-static"
:base-directory ,blog-static-directory
:base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg\\|swf\\|svg\\|woff\\|woff2\\|ico\\|webp\\|avif"
:publishing-directory ,(concat blog-publish-directory "static/")
:recursive t
:publishing-function org-publish-attachment)
("blog" :components ("blog-posts" "blog-pages" "blog-static"))))
;; Main publishing function
(defun blog/publish-all ()
"Publish the entire blog."
(org-publish "blog" t)
(message "Blog published successfully!"))
(provide 'build)
;;; build.el ends here