Introduction
Introduction Statistics Contact Development Disclaimer Help
generator.lisp - cl-yag - Common Lisp Yet Another website Generator
git clone git://bitreich.org/cl-yag/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws…
Log
Files
Refs
Tags
README
LICENSE
---
generator.lisp (20812B)
---
1 ;;;; GLOBAL VARIABLES
2
3 (defparameter *articles* '())
4 (defparameter *converters* '())
5 (defparameter *days* '("Monday" "Tuesday" "Wednesday" "Thursday"
6 "Friday" "Saturday" "Sunday"))
7 (defparameter *months* '("January" "February" "March" "April"
8 "May" "June" "July" "August" "September"
9 "October" "November" "December"))
10
11 ;; structure to store links
12 (defstruct article title tag date id tiny author rawdate converter)
13 (defstruct converter name command extension)
14
15 ;;;; FUNCTIONS
16
17 (require 'asdf)
18
19 ;; return the day of the week
20 (defun get-day-of-week(day month year)
21 (multiple-value-bind
22 (second minute hour date month year day-of-week dst-p tz)
23 (decode-universal-time (encode-universal-time 0 0 0 day month year))
24 (declare (ignore second minute hour date month year dst-p tz))
25 day-of-week))
26
27 ;; parse the date to
28 (defun date-parse(date)
29 (if (= 8 (length date))
30 (let* ((year (parse-integer date :start 0 :end 4))
31 (monthnum (parse-integer date :start 4 :end 6))
32 (daynum (parse-integer date :start 6 :end 8))
33 (day (nth (get-day-of-week daynum monthnum year) *days…
34 (month (nth (- monthnum 1) *months*)))
35 (list
36 :dayname day
37 :daynumber daynum
38 :monthname month
39 :monthnumber monthnum
40 :year year))
41 nil))
42
43 (defun post(&optional &key title tag date id (tiny nil) (author (getf *c…
44 (push (make-article :title title
45 :tag tag
46 :date (date-parse date)
47 :rawdate date
48 :tiny tiny
49 :author author
50 :id id
51 :converter converter)
52 *articles*))
53
54 ;; we add a converter to the list of the one availables
55 (defun converter(&optional &key name command extension)
56 (setf *converters*
57 (append
58 (list name
59 (make-converter :name name
60 :command command
61 :extension extension))
62 *converters*)))
63
64 ;; load data from metadata and load config
65 (load "data/articles.lisp")
66 (setf *articles* (reverse *articles*))
67
68
69 ;; common-lisp don't have a replace string function natively
70 (defun replace-all (string part replacement &key (test #'char=))
71 (with-output-to-string (out)
72 (loop with part-length = (length part)
73 for old-pos = 0 then (+ pos part-length)
74 for pos = (search part string
75 :start2 old-pos
76 :test test)
77 do (write-string string out
78 :start old-pos
79 :end (or pos (length string)))
80 when pos do (write-string replacement out)
81 while pos)))
82
83 ;; common-lisp don't have a split string function natively
84 (defun split-str(text &optional (separator #\Space))
85 "this function split a string with separator and return a list"
86 (let ((text (concatenate 'string text (string separator))))
87 (loop for char across text
88 counting char into count
89 when (char= char separator)
90 collect
91 ;; we look at the position of the left separator from right to le…
92 (let ((left-separator-position (position separator text :from-e…
93 (subseq text
94 ;; if we can't find a separator at the left of the cu…
95 ;; the string
96 (if left-separator-position (+ 1 left-separator-posit…
97 (- count 1))))))
98
99 ;; load a file as a string
100 ;; we escape ~ to avoid failures with format
101 (defun load-file(path)
102 (if (probe-file path)
103 (with-open-file (stream path)
104 (let ((contents (make-string (file-length stream))))
105 (read-sequence contents stream)
106 contents))
107 (progn
108 (format t "ERROR : file ~a not found. Aborting~%" path)
109 (quit))))
110
111 ;; save a string in a file
112 (defun save-file(path data)
113 (with-open-file (stream path :direction :output :if-exists :supersede)
114 (write-sequence data stream)))
115
116 ;; simplify the str replace work
117 (defmacro template(before &body after)
118 `(progn
119 (setf output (replace-all output ,before ,@after))))
120
121 ;; get the converter object of "article"
122 (defmacro with-converter(&body code)
123 `(progn
124 (let ((converter-name (if (article-converter article)
125 (article-converter article)
126 (getf *config* :default-converter))))
127 (let ((converter-object (getf *converters* converter-name)))
128 ,@code))))
129
130 ;; generate the html file from the source file
131 ;; using the converter associated with the post
132 (defun use-converter-to-html(filename &optional (converter-name nil))
133 (let* ((converter-object (getf *converters*
134 (or converter-name
135 converter-name
136 (getf *config* :default-converter))…
137 (output (converter-command converter-object))
138 (src-file (format nil "~a~a" filename (converter-extension conv…
139 (dst-file (format nil "temp/data/~a.html" filename ))
140 (full-src-file (format nil "data/~a" src-file)))
141 ;; skip generating if the destination exists
142 ;; and is more recent than source
143 (unless (and
144 (probe-file dst-file)
145 (>=
146 (file-write-date dst-file)
147 (file-write-date full-src-file)))
148 (ensure-directories-exist "temp/data/")
149 (template "%IN" src-file)
150 (template "%OUT" dst-file)
151 (format t "~a~%" output)
152 (uiop:run-program output))))
153
154 ;; format the date
155 (defun date-format(format date)
156 (let ((output format))
157 (template "%DayName" (getf date :dayname))
158 (template "%DayNumber" (format nil "~2,'0d" (getf date :daynumber)…
159 (template "%MonthName" (getf date :monthname))
160 (template "%MonthNumber" (format nil "~2,'0d" (getf date :monthnumbe…
161 (template "%Year" (write-to-string (getf date :year )))
162 output))
163
164 ;; simplify the declaration of a new page type
165 (defmacro prepare(template &body code)
166 `(progn
167 (let ((output (load-file ,template)))
168 ,@code
169 output)))
170
171 ;; simplify the file saving by using the layout
172 (defmacro generate(name &body data)
173 `(progn
174 (save-file ,name (generate-layout ,@data))))
175
176 ;; generate a gemini index file
177 (defun generate-gemini-index(articles)
178 (let ((output (load-file "templates/gemini_head.tpl")))
179 (dolist (article articles)
180 (setf output
181 (string
182 (concatenate 'string output
183 (format nil "=> ~a/articles/~a.gmi ~a-~2,'0d-~…
184 (getf *config* :gemini-path)
185 (article-id article)
186 (getf (article-date article) :year)
187 (getf (article-date article) :monthnum…
188 (getf (article-date article) :daynumbe…
189 (article-title article))))))
190 output))
191
192 ;; generate a gopher index file
193 (defun generate-gopher-index(articles)
194 (let ((output (load-file "templates/gopher_head.tpl")))
195 (dolist (article articles)
196 (setf output
197 (string
198 (concatenate 'string output
199 (format nil (getf *config* :gopher-format)
200 0 ;;;; gopher type, 0 for text files
201 ;; here we create a 80 width char stri…
202 ;; and date on the right
203 ;; we truncate the article title if it…
204 (let ((title (format nil "~80a"
205 (if (< 80 (length…
206 (subseq (arti…
207 (article-titl…
208 (replace title (article-rawdate arti…
209 (concatenate 'string
210 (getf *config* :gopher-pa…
211 (getf *config* :gopher-server)
212 (getf *config* :gopher-port)
213 )))))
214 output))
215
216 ;; generate the list of tags
217 (defun articles-by-tag()
218 (let ((tag-list))
219 (loop for article in *articles* do
220 (when (article-tag article) ;; we don't want an error if no tag
221 (loop for tag in (split-str (article-tag article)) do ;; for…
222 (setf (getf tag-list (intern tag "KEYWORD")) ;; we cre…
223 (list
224 :name tag
225 :value (push (article-id article) (getf (getf t…
226 (loop for i from 1 to (length tag-list) by 2 collect ;; removing the…
227 (nth i tag-list))))
228
229 ;; generates the html of the list of tags for an article
230 (defun get-tag-list-article(&optional article)
231 (apply #'concatenate 'string
232 (mapcar #'(lambda (item)
233 (prepare "templates/one-tag.tpl" (template "%%Name%…
234 (split-str (article-tag article)))))
235
236 ;; generates the html of the whole list of tags
237 (defun get-tag-list()
238 (apply #'concatenate 'string
239 (mapcar #'(lambda (item)
240 (prepare "templates/one-tag.tpl"
241 (template "%%Name%%" (getf item :name))))
242 (articles-by-tag))))
243
244
245 ;; generates the html of only one article
246 ;; this is called in a loop to produce the homepage
247 (defun create-article(article &optional &key (tiny t) (no-text nil))
248 (prepare "templates/article.tpl"
249 (template "%%Author%%" (let ((author (article-author article)…
250 (or author (getf *config* :webmaster…
251 (template "%%Date%%" (date-format (getf *config* :date-form…
252 (article-date article)))
253 (template "%%Raw-Date%%" (article-rawdate article))
254 (template "%%Title%%" (article-title article))
255 (template "%%Id%%" (article-id article))
256 (template "%%Tags%%" (get-tag-list-article article))
257 (template "%%Date-Url%%" (date-format "%Year-%MonthNumber-%D…
258 (article-date article)…
259 (template "%%Text%%" (if no-text
260 ""
261 (if (and tiny (article-tiny articl…
262 (format nil "<p>~a</p>" (artic…
263 (load-file (format nil "temp/d…
264
265 ;; return a html string
266 ;; produce the code of a whole page with title+layout with the parameter…
267 (defun generate-layout(body &optional &key (title nil))
268 (prepare "templates/layout.tpl"
269 (template "%%Title%%" (if title title (getf *config* :title)))
270 (template "%%Tags%%" (get-tag-list))
271 (template "%%Body%%" body)
272 output))
273
274
275 ;; html generation of index homepage
276 (defun generate-semi-mainpage(&key (tiny t) (no-text nil))
277 (apply #'concatenate 'string
278 (loop for article in *articles* collect
279 (create-article article :tiny tiny :no-text no-text))))
280
281 ;; html generation of a tag homepage
282 (defun generate-tag-mainpage(articles-in-tag)
283 (apply #'concatenate 'string
284 (loop for article in *articles*
285 when (member (article-id article) articles-in-tag :test #'eq…
286 collect (create-article article :tiny t))))
287
288 ;; xml generation of the items for the rss
289 (defun generate-rss-item(&key (gopher nil))
290 (apply #'concatenate 'string
291 (loop for article in *articles*
292 for i from 1 to (min (length *articles*) (getf *config* :rss…
293 collect
294 (prepare "templates/rss-item.tpl"
295 (template "%%Title%%" (article-title article))
296 (template "%%Description%%" (load-file (format ni…
297 (template "%%Date%%" (format nil
298 (date-format "~a, %D…
299 (articl…
300 (subseq (getf (artic…
301 (subseq (getf (artic…
302 (template "%%Url%%"
303 (if gopher
304 (format nil "gopher://~a:~d/0~a/art…
305 (getf *config* :gopher-serv…
306 (getf *config* :gopher-port)
307 (getf *config* :gopher-path)
308 (article-id article))
309 (format nil "~d~d-~d.html"
310 (getf *config* :url)
311 (date-format "%Year-%MonthN…
312 (article-date …
313 (article-id article))))))))
314
315
316 ;; Generate the rss xml data
317 (defun generate-rss(&key (gopher nil))
318 (prepare "templates/rss.tpl"
319 (template "%%Description%%" (getf *config* :description))
320 (template "%%Title%%" (getf *config* :title))
321 (template "%%Url%%" (getf *config* :url))
322 (template "%%Items%%" (generate-rss-item :gopher gopher))))
323
324 ;; We do all the website
325 (defun create-html-site()
326
327 ;; produce each article file
328 (loop for article in *articles*
329 do
330 ;; use the article's converter to get html code of it
331 (use-converter-to-html (article-id article) (article-converter ar…
332
333 (generate (format nil "output/html/~d-~d.html"
334 (date-format "%Year-%MonthNumber-%DayNumber"
335 (article-date article))
336 (article-id article))
337 (create-article article :tiny nil)
338 :title (concatenate 'string (getf *config* :title) " …
339
340 ;; produce index.html
341 (generate "output/html/index.html" (generate-semi-mainpage))
342
343 ;; produce index-titles.html where there are only articles titles
344 (generate "output/html/index-titles.html" (generate-semi-mainpage :no-…
345
346 ;; produce index file for each tag
347 (loop for tag in (articles-by-tag) do
348 (generate (format nil "output/html/tag-~d.html" (getf tag :NAME))
349 (generate-tag-mainpage (getf tag :VALUE))))
350
351 ;; generate rss gopher in html folder if gopher is t
352 (when (getf *config* :gopher)
353 (save-file "output/html/rss-gopher.xml" (generate-rss :gopher t)))
354
355 ;;(generate-file-rss)
356 (save-file "output/html/rss.xml" (generate-rss)))
357
358 ;; we do all the gemini capsule
359 (defun create-gemini-capsule()
360
361 ;; produce the index.gmi file
362 (save-file (concatenate 'string "output/gemini/" (getf *config* :gemin…
363 (generate-gemini-index *articles*))
364
365 ;; produce a tag list menu
366 (let* ((directory-path "output/gemini/_tags_/")
367 (index-path (concatenate 'string directory-path (getf *config* …
368 (ensure-directories-exist directory-path)
369 (save-file index-path
370 (let ((output (load-file "templates/gemini_head.tpl")))
371 (loop for tag in
372 ;; sort tags per articles in it
373 (sort (articles-by-tag) #'>
374 :key #'(lambda (x) (length (getf x :value))))
375 do
376 (setf output
377 (string
378 (concatenate
379 'string output
380 (format nil "=> ~a/~a/index.gmi ~a ~d~%"
381 (getf *config* :gemini-path)
382 (getf tag :name)
383 (getf tag :name)
384 (length (getf tag :value)))))))
385 output)))
386
387 ;; produce each tag gemini index
388 (loop for tag in (articles-by-tag) do
389 (let* ((directory-path (concatenate 'string "output/gemini/" (get…
390 (index-path (concatenate 'string directory-path (getf *con…
391 (articles-with-tag (loop for article in *articles*
392 when (member (article-id article) (g…
393 collect article)))
394 (ensure-directories-exist directory-path)
395 (save-file index-path (generate-gemini-index articles-with-tag)…
396
397 ;; produce each article file (adding some headers)
398 (loop for article in *articles*
399 do
400 (with-converter
401 (let ((id (article-id article)))
402 (save-file (format nil "output/gemini/articles/~a.gmi" id)
403 (format nil "~{~a~}"
404 (list
405 "Title : " (article-title article) #\Ne…
406 "Author: " (article-author article) #\N…
407 "Date : " (date-format (getf *config* …
408 "Tags : " (article-tag article) #\Newl…
409 (load-file (format nil "data/~d~d" id (…
410
411 ;; we do all the gopher hole
412 (defun create-gopher-hole()
413
414 ;;(generate-file-rss)
415 (save-file "output/gopher/rss.xml" (generate-rss :gopher t))
416
417 ;; produce the gophermap file
418 (save-file (concatenate 'string "output/gopher/" (getf *config* :gophe…
419 (generate-gopher-index *articles*))
420
421 ;; produce a tag list menu
422 (let* ((directory-path "output/gopher/_tags_/")
423 (index-path (concatenate 'string directory-path (getf *config* …
424 (ensure-directories-exist directory-path)
425 (save-file index-path
426 (let ((output (load-file "templates/gopher_head.tpl")))
427 (loop for tag in
428 ;; sort tags per articles in it
429 (sort (articles-by-tag) #'>
430 :key #'(lambda (x) (length (getf x :value))))
431 do
432 (setf output
433 (string
434 (concatenate
435 'string output
436 (format nil (getf *config* :gopher-format)
437 1 ;; gopher type, 1 for menus
438 ;; here we create a 72 width char …
439 ;; and number of articles on the r…
440 ;; we truncate the article title i…
441 (let ((title (format nil "~72a"
442 (if (< 72 (le…
443 (subseq (…
444 (getf tag…
445 (article-number (format nil …
446 (replace title article-number :s…
447 (concatenate 'string
448 (getf *config* :gophe…
449 (getf *config* :gopher-server)
450 (getf *config* :gopher-port)
451 )))))
452 output)))
453
454 ;; produce each tag gophermap index
455 (loop for tag in (articles-by-tag) do
456 (let* ((directory-path (concatenate 'string "output/gopher/" (get…
457 (index-path (concatenate 'string directory-path (getf *con…
458 (articles-with-tag (loop for article in *articles*
459 when (member (article-id article) (g…
460 collect article)))
461 (ensure-directories-exist directory-path)
462 (save-file index-path (generate-gopher-index articles-with-tag)…
463
464 ;; produce each article file (adding some headers)
465 (loop for article in *articles*
466 do
467 (with-converter
468 (let ((id (article-id article)))
469 (save-file (format nil "output/gopher/article-~d.txt" id)
470 (format nil "Title: ~a~%Author: ~a~%Date: ~a~%Ta…
471 (article-title article)
472 (article-author article)
473 (date-format (getf *config* :date-forma…
474 (article-tag article)
475 (load-file (format nil "data/~d…
476
477
478 ;; This is function called when running the tool
479 (defun generate-site()
480 (if (getf *config* :html)
481 (create-html-site))
482 (if (getf *config* :gemini)
483 (create-gemini-capsule))
484 (if (getf *config* :gopher)
485 (create-gopher-hole)))
486
487 ;;;; EXECUTION
488
489 (generate-site)
490
491 (quit)
You are viewing proxied material from bitreich.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.