clone url: git://git.m455.casa/lol
src/main.scm
1 | (import utf8 |
2 | spiffy |
3 | lowdown |
4 | (chicken sort) |
5 | (chicken string) |
6 | (chicken irregex) |
7 | (chicken io) |
8 | (chicken format) |
9 | (chicken file) |
10 | (chicken port) |
11 | (chicken pathname) |
12 | (chicken time posix) |
13 | (chicken process) |
14 | (chicken process-context)) |
15 |
|
16 | (include "src/static.scm") |
17 | (include "src/utils.scm") |
18 |
|
19 | (define (markup->html source-file-contents) |
20 | (with-output-to-string ;; left off here |
21 | (lambda () (markdown->html source-file-contents)))) |
22 |
|
23 | (define (markup->rss-item source-pathname) |
24 | (let ((markup-body (string-populate (file->string source-pathname) *config-data*))) |
25 | (string-populate |
26 | (file->string (get *config-data* 'rss-item-template)) |
27 | `((page-title ,(string->title markup-body)) |
28 | (page-url ,(pathname->url source-pathname 'with-https-prefix)) |
29 | (page-date ,(date->rss-date (string->date markup-body))))))) |
30 |
|
31 | ;; this populates variables in the html template file, and the populates the |
32 | ;; variables in the body contents, which are created by people in their source |
33 | ;; files |
34 | ;; |
35 | ;; note: the title of the source page should exist on the first line in the |
36 | ;; file, because the source page is checked by string->title to |
37 | ;; extract the title of the post |
38 | (define (populate-html-page source-file-contents) |
39 | (string-populate |
40 | (file->string (get *config-data* 'html-page-template)) |
41 | (append `((page-contents ,(markup->html source-file-contents)) |
42 | (rss-file ,(get *config-data* 'rss-file)) |
43 | (css ,(file->string (get *config-data* 'css-file))) |
44 | (page-title ,(string->title source-file-contents))) |
45 | *config-data*))) |
46 |
|
47 | ;; source-pathname = source/posts/2021/somefile.txt |
48 | (define (create-html-file source-pathname) |
49 | (let* ((source-file-contents (file->string source-pathname)) |
50 | (destination-pathname-markup (pathname->destination-pathname source-pathname)) ;; => build/posts/2021/somefile.txt |
51 | (destination-pathname (pathname-replace-extension destination-pathname-markup "html")) ;; => build/posts/2021/somefile.html |
52 | (html-content (populate-html-page source-file-contents))) |
53 | (file-write destination-pathname html-content))) |
54 |
|
55 | (define (generate-rss-feed source-posts) |
56 | (let* ((rss-feed-without-items (string-populate |
57 | (file->string (get *config-data* 'rss-channel-template)) |
58 | `((domain ,(get *config-data* 'domain)) |
59 | (rss-file ,(get *config-data* 'rss-file))))) |
60 | (rss-items-list (map markup->rss-item source-posts)) |
61 | (rss-feed (string-populate |
62 | rss-feed-without-items |
63 | `((rss-items ,(string-intersperse rss-items-list "\n"))))) |
64 | (rss-pathname (make-pathname (get *config-data* 'build-directory) (get *config-data* 'rss-file)))) |
65 | (file-write rss-pathname rss-feed))) |
66 |
|
67 | ;; takes a list like this: |
68 | ;; '("/posts/2022/hey.txt" |
69 | ;; "/posts/2021/yep.txt" |
70 | ;; "/posts/2020/fuck.txt") |
71 | (define (generate-posts-page source-posts) |
72 | (let* ((links-list (map (lambda (post-pathname) |
73 | (let ((title (string->title (file->string post-pathname))) |
74 | (url (pathname->url post-pathname 'no-prefix))) |
75 | (format "- [~a](~a)" title url))) |
76 | source-posts)) |
77 | (links-str (string-intersperse links-list "\n")) |
78 | (markup (string-populate |
79 | (file->string (get *config-data* 'posts-page-template)) |
80 | `((links ,links-str))))) |
81 | (file-write (make-pathname |
82 | (pathname->destination-pathname (get *config-data* 'posts-directory)) |
83 | "index" |
84 | "html") |
85 | (populate-html-page markup)))) |
86 |
|
87 | ;; first, create a list like this: |
88 | ;; '((20210122 "/posts/2021/yep.txt") |
89 | ;; (20220122 "/posts/2022/hey.txt") |
90 | ;; (20200122 "/posts/2020/fuck.txt")) |
91 | ;; , then reverse sort it by the dates (the car of the list) |
92 | ;; , and then remove the dates, so you get a list like this: |
93 | ;; '("/posts/2022/hey.txt" |
94 | ;; "/posts/2021/yep.txt" |
95 | ;; "/posts/2020/fuck.txt") |
96 | (define (reverse-sort-posts source-posts) |
97 | (let ((dates-and-pathnames (map (lambda (pathname) |
98 | (list (string->date (file->string pathname)) |
99 | pathname)) |
100 | source-posts))) |
101 | (map cadr (sort dates-and-pathnames |
102 | (lambda (date-path-pair-x date-path-pair-y) |
103 | (string>? (car date-path-pair-x) (car date-path-pair-y))))))) |
104 |
|
105 | ;; commands ==================================================================== |
106 | ;; preview ------------------------------------------------- |
107 | (define (preview) |
108 | (set! *config-data* (load-config-file)) |
109 | (server-port 8000) |
110 | (root-path (get *config-data* 'build-directory)) |
111 | (displayln "you can preview your website at http://127.0.0.1:8000") |
112 | (displayln "press ctrl-c to stop the server") |
113 | (start-server)) |
114 |
|
115 | ;; build --------------------------------------------------- |
116 | (define (build) |
117 | (set! *config-data* (load-config-file)) |
118 | (let* ((source-pathnames (find-files (get *config-data* 'source-directory))) |
119 | (assets-pathnames (find-files (get *config-data* 'assets-directory))) |
120 | (source-files (filter (lambda (path) |
121 | (and (file-exists? path) |
122 | (not (directory-exists? path)))) |
123 | source-pathnames)) |
124 | (assets-files (filter (lambda (pathname) |
125 | (and (file-exists? pathname) |
126 | (not (directory-exists? pathname)))) |
127 | assets-pathnames)) |
128 | (directories (append (filter directory-exists? source-pathnames) |
129 | (filter directory-exists? assets-pathnames)))) |
130 |
|
131 | ;; clean build directory |
132 | (when (equal? (get *config-data* 'clean-build-directory?) "yes") |
133 | (let ((build-dir (get *config-data* 'build-directory))) |
134 | (displayln-format "clearing your ~a directory..." build-dir) |
135 | (delete-directory build-dir #t) |
136 | (create-directory build-dir #t))) |
137 |
|
138 | ;; copy directory structure of source and asset directories |
139 | (when (not (null? directories)) |
140 | (displayln-format "copying the directory structure of your ~a and ~a directories..." |
141 | (get *config-data* 'source-directory) |
142 | (get *config-data* 'assets-directory)) |
143 | (for-each (lambda (dir) (create-directory dir #t)) |
144 | (map pathname->destination-pathname directories))) |
145 |
|
146 | ;; copy assets over |
147 | (when (not (null? assets-files)) |
148 | (displayln "copying assets...") |
149 | (for-each (lambda (file) (copy-file file (pathname->destination-pathname file) #t)) |
150 | assets-files)) |
151 |
|
152 | ;; process source files |
153 | (when (not (null? source-files)) |
154 | ;; generate html files |
155 | (displayln "generating html files...") |
156 | (for-each create-html-file source-files) |
157 |
|
158 | (let ((source-posts (reverse-sort-posts |
159 | (filter (lambda (path) |
160 | (and (file-exists? path) |
161 | (not (directory-exists? path)))) |
162 | (find-files (get *config-data* 'posts-directory)))))) |
163 | ;; generate list of posts at build/posts/index.html |
164 | (when (equal? (get *config-data* 'generate-posts-page?) "yes") |
165 | (displayln "building a list of posts...") |
166 | (generate-posts-page source-posts) |
167 |
|
168 | ;; generate rss feed |
169 | (when (equal? (get *config-data* 'generate-rss-feed?) "yes") |
170 | (displayln "building an rss feed...") |
171 | (generate-rss-feed source-posts))))) |
172 | (displayln "done building your website!"))) |
173 |
|
174 | ;; init ---------------------------------------------------- |
175 | (define (init) |
176 | (if (file-exists? CONFIG-FILE) |
177 | (set! *config-data* (load-config-file)) |
178 | (begin (file-write CONFIG-FILE *config-data*) |
179 | (set! *config-data* (load-config-file)))) |
180 |
|
181 | (for-each (lambda (directory) (create-directory directory #t)) |
182 | (list (get *config-data* 'build-directory) |
183 | (get *config-data* 'source-directory) |
184 | (get *config-data* 'posts-directory) |
185 | (get *config-data* 'assets-directory) |
186 | (get *config-data* 'templates-directory))) |
187 |
|
188 | (for-each (lambda (pair) |
189 | (let* ((file (car pair)) |
190 | (contents (cadr pair)) |
191 | (directory (pathname-directory file))) |
192 | (when directory (create-directory directory #t)) |
193 | (when (not (file-exists? file)) |
194 | (file-write file contents)))) |
195 | (list (list (get *config-data* 'css-file) DEFAULT-CONTENTS-CSS) |
196 | (list (get *config-data* 'html-page-template) DEFAULT-CONTENTS-HTML-PAGE) |
197 | (list (get *config-data* 'posts-page-template) DEFAULT-CONTENTS-POSTS-PAGE) |
198 | (list (get *config-data* 'rss-channel-template) DEFAULT-CONTENTS-RSS-CHANNEL) |
199 | (list (get *config-data* 'rss-item-template) DEFAULT-CONTENTS-RSS-ITEM))) |
200 |
|
201 | (displayln "initialized default files and directory for your website!")) |
202 |
|
203 | ;; help --------------------------------------------------- |
204 | (define help-message |
205 | #<<string-block |
206 | help - display this help message. |
207 | init - create the files and directories that are required for generating a website. |
208 | build - generate a website |
209 | preview - preview your website on a local http server |
210 | string-block |
211 | ) |
212 |
|
213 | (define (main args) |
214 | (if (null? args) |
215 | (displayln help-message) |
216 | (case (string->symbol (car args)) |
217 | ((--help -help help -h) |
218 | (displayln help-message)) |
219 | ('init (init)) |
220 | ('build (build)) |
221 | ('preview (preview)) |
222 | (else (displayln help-message))))) |
223 |
|
224 | (main (command-line-arguments)) |