cl-yag/generator.lisp

258 lines
9.4 KiB
Common Lisp
Raw Normal View History

2017-12-12 18:37:45 +00:00
(defparameter *articles* '())
(defparameter *converters* '())
2017-12-12 18:37:45 +00:00
;; structure to store links
(defstruct article title tag date id tiny author short)
(defstruct converter name command extension)
2017-12-12 18:37:45 +00:00
(defun post(&optional &key title tag date id (tiny nil) (author nil) (short nil))
(push (make-article :title title
:tag tag
:date date
:tiny tiny
:author author
:short short
:id id)
*articles*))
;; we add a converter to the list of the one availables
(defun converter(&optional &key name command extension)
(push (make-converter :name name
:command command
:extension extension)
*converters*))
2016-04-30 15:21:31 +00:00
(load "data/articles.lisp")
(setf *articles* (reverse *articles*))
2016-04-30 15:21:31 +00:00
2017-12-12 18:37:45 +00:00
2016-04-30 16:21:48 +00:00
;; common-lisp don't have a replace string function natively
2016-04-30 15:21:31 +00:00
(defun replace-all (string part replacement &key (test #'char=))
(with-output-to-string (out)
(loop with part-length = (length part)
for old-pos = 0 then (+ pos part-length)
for pos = (search part string
:start2 old-pos
:test test)
do (write-string string out
:start old-pos
:end (or pos (length string)))
when pos do (write-string replacement out)
while pos)))
2016-05-03 12:03:25 +00:00
;; common-lisp don't have a split string function natively
2017-11-28 06:21:33 +00:00
(defun split-str(text &optional (separator #\Space))
"this function split a string with separator and return a list"
(let ((text (concatenate 'string text (string separator))))
(loop for char across text
counting char into count
when (char= char separator)
collect
;; we look at the position of the left separator from right to left
(let ((left-separator-position (position separator text :from-end t :end (- count 1))))
(subseq text
;; if we can't find a separator at the left of the current, then it's the start of
;; the string
(if left-separator-position (+ 1 left-separator-position) 0)
(- count 1))))))
2016-05-03 12:03:25 +00:00
2016-05-03 13:54:02 +00:00
;; load a file as a string
;; we escape ~ to avoid failures with format
(defun load-file(path)
2017-01-23 14:00:40 +00:00
(if (probe-file path)
(replace-all
2017-11-28 06:33:25 +00:00
(apply #'concatenate 'string
(with-open-file (stream path)
(loop for line = (read-line stream nil)
while line
collect
(format nil "~a~%" line))))
2017-01-23 14:00:40 +00:00
"~" "~~")
(progn
(format t "ERROR : file ~a not found. Aborting~%" path)
(quit))))
2016-05-03 13:54:02 +00:00
2016-04-30 16:21:48 +00:00
;; save a string in a file
2016-04-30 15:21:31 +00:00
(defun save-file(path data)
2016-08-11 12:51:05 +00:00
(with-open-file (stream path :direction :output :if-exists :supersede)
2017-11-03 13:08:48 +00:00
(format stream data)))
2016-04-30 15:21:31 +00:00
;; simplify the str replace work
(defmacro template(before &body after)
`(progn
(setf output (replace-all output ,before ,@after))))
;; simplify the declaration of a new page type
(defmacro prepare(template &body code)
`(progn
(let ((output (load-file ,template)))
2016-04-30 15:21:31 +00:00
,@code
output)))
;; simplify the file saving by using the layout
(defmacro generate(name &body data)
`(progn
2016-05-03 13:54:02 +00:00
(save-file ,name (generate-layout ,@data))))
2016-05-03 12:03:25 +00:00
;; generate the list of tags
(defun articles-by-tag()
2016-05-03 12:03:25 +00:00
(let ((tag-list))
(loop for article in *articles* do
2017-12-12 18:37:45 +00:00
(when (article-tag article) ;; we don't want an error if no tag
(loop for tag in (split-str (article-tag article)) do ;; for each word in tag keyword
(setf (getf tag-list (intern tag "KEYWORD")) ;; we create the keyword is inexistent and add ID to :value
(list
:name tag
2017-12-12 18:37:45 +00:00
:value (push (article-id article) (getf (getf tag-list (intern tag "KEYWORD")) :value)))))))
2016-05-03 12:03:25 +00:00
(loop for i from 1 to (length tag-list) by 2 collect ;; removing the keywords
(nth i tag-list))))
2017-11-03 13:08:48 +00:00
;; generates the html of the list of tags for an article
(defun get-tag-list-article(&optional article)
2017-11-28 06:33:25 +00:00
(apply #'concatenate 'string
(mapcar #'(lambda (item)
(prepare "templates/one-tag.tpl" (template "%%Name%%" item)))
2017-12-12 18:37:45 +00:00
(split-str (article-tag article)))))
;; generates the html of the whole list of tags
(defun get-tag-list()
2017-11-28 06:33:25 +00:00
(apply #'concatenate 'string
(mapcar #'(lambda (item)
(prepare "templates/one-tag.tpl"
(template "%%Name%%" (getf item :name))))
(articles-by-tag))))
2017-11-03 13:08:48 +00:00
2016-04-30 15:21:31 +00:00
;; generates the html of one only article
2016-04-30 16:21:48 +00:00
;; this is called in a loop to produce the homepage
2017-01-21 15:53:27 +00:00
(defun create-article(article &optional &key (tiny t) (no-text nil))
2017-11-17 14:51:35 +00:00
(prepare "templates/article.tpl"
2017-12-12 18:37:45 +00:00
(template "%%Author%%" (let ((author (article-author article)))
(or author (getf *config* :webmaster))))
(template "%%Date%%" (article-date article))
(template "%%Title%%" (article-title article))
(template "%%Id%%" (article-id article))
2017-11-03 13:08:48 +00:00
(template "%%Tags%%" (get-tag-list-article article))
(template "%%Text%%" (if no-text
""
2017-12-12 18:37:45 +00:00
(if (and tiny (article-tiny article))
(article-tiny article)
(load-file (format nil "temp/data/~d.html" (article-id article))))))))
2016-04-30 15:21:31 +00:00
2016-04-30 16:21:48 +00:00
;; return a html string
;; produce the code of a whole page with title+layout with the parameter as the content
(defun generate-layout(body &optional &key (title nil))
2017-11-17 14:51:35 +00:00
(prepare "templates/layout.tpl"
(template "%%Title%%" (if title title (getf *config* :title)))
(template "%%Tags%%" (get-tag-list))
(template "%%Body%%" body)
output))
2016-04-30 16:21:48 +00:00
2016-05-03 12:03:25 +00:00
;; html generation of index homepage
2017-01-21 15:53:27 +00:00
(defun generate-semi-mainpage(&key (tiny t) (no-text nil))
2017-11-28 06:33:25 +00:00
(apply #'concatenate 'string
(loop for article in *articles* collect
(create-article article :tiny tiny :no-text no-text))))
2016-05-03 12:03:25 +00:00
;; html generation of a tag homepage
(defun generate-tag-mainpage(articles-in-tag)
2017-11-28 06:33:25 +00:00
(apply #'concatenate 'string
(loop for article in *articles*
2017-12-12 18:37:45 +00:00
when (member (article-id article) articles-in-tag :test #'equal)
2017-11-28 06:33:25 +00:00
collect (create-article article :tiny t))))
2016-05-03 12:03:25 +00:00
;; xml generation of the items for the rss
(defun generate-rss-item()
2017-11-28 06:33:25 +00:00
(apply #'concatenate 'string
(loop for article in *articles*
for i from 1 to (if (> (length *articles*) (getf *config* :rss-item-number)) (getf *config* :rss-item-number) (length *articles*))
collect
(prepare "templates/rss-item.tpl"
2017-12-12 18:37:45 +00:00
(template "%%Title%%" (article-title article))
(template "%%Description%%" (load-file (format nil "temp/data/~d.html" (article-id article))))
2017-11-28 06:33:25 +00:00
(template "%%Url%%"
(format nil "~darticle-~d.html"
(getf *config* :url)
2017-12-12 18:37:45 +00:00
(article-id article)))))))
2017-11-28 06:33:25 +00:00
;; Generate the rss xml data
(defun generate-rss()
2017-11-17 14:51:35 +00:00
(prepare "templates/rss.tpl"
(template "%%Description%%" (getf *config* :description))
(template "%%Title%%" (getf *config* :title))
(template "%%Url%%" (getf *config* :url))
(template "%%Items%%" (generate-rss-item))))
2016-04-30 15:21:31 +00:00
2016-08-11 12:51:05 +00:00
;; We do all the website
(defun create-html-site()
;; produce index.html
2016-08-11 12:51:05 +00:00
(generate "output/html/index.html" (generate-semi-mainpage))
2017-01-21 15:53:27 +00:00
;; produce index-titles.html where there are only articles titles
(generate "output/html/index-titles.html" (generate-semi-mainpage :no-text t))
;; produce each article file
2016-04-30 16:12:28 +00:00
(dolist (article *articles*)
2017-12-12 18:37:45 +00:00
(generate (format nil "output/html/article-~d.html" (article-id article))
(create-article article :tiny nil)
2017-12-12 18:37:45 +00:00
:title (concatenate 'string (getf *config* :title) " : " (article-title article))))
2016-08-11 12:51:05 +00:00
2016-05-03 12:03:25 +00:00
;; produce index file for each tag
(loop for tag in (articles-by-tag) do
2016-08-11 12:51:05 +00:00
(generate (format nil "output/html/tag-~d.html" (getf tag :NAME))
2016-05-03 12:03:25 +00:00
(generate-tag-mainpage (getf tag :VALUE))))
2016-04-30 15:21:31 +00:00
;;(generate-file-rss)
2016-08-11 12:51:05 +00:00
(save-file "output/html/rss.xml" (generate-rss)))
;; we do all the gopher hole
(defun create-gopher-hole()
;; produce the gophermap file
(save-file (concatenate 'string "output/gopher/" (getf *config* :gopher-index))
2017-11-17 14:51:35 +00:00
(let ((output (load-file "templates/gopher_head.tpl")))
2016-08-11 12:51:05 +00:00
(dolist (article *articles*)
(setf output
2017-03-24 12:07:52 +00:00
(string
(concatenate 'string output
(format nil (getf *config* :gopher-format)
2017-03-24 12:07:52 +00:00
;; here we create a 80 width char string with title on the left
;; and date on the right
;; we truncate the article title if it's too large
(let ((title (format nil "~80a"
2017-12-12 18:37:45 +00:00
(if (< 80 (length (article-title article)))
(subseq (article-title article) 0 80)
(article-title article)))))
(replace title (article-date article) :start1 (- (length title) (length (article-date article)))))
2017-03-24 12:07:52 +00:00
(getf *config* :gopher-path)
2017-12-12 18:37:45 +00:00
(article-id article)
2017-03-24 12:07:52 +00:00
(getf *config* :gopher-server)
(getf *config* :gopher-port)
2017-11-03 13:09:14 +00:00
)))))
2016-08-11 12:51:05 +00:00
output))
;; produce each article file (only a copy/paste in fact)
(dolist (article *articles*)
2017-12-12 18:37:45 +00:00
(let ((id (article-id article)))
2016-08-11 12:51:05 +00:00
(save-file (format nil "output/gopher/article-~d.txt" id)
(load-file (format nil "data/~d.md" id)))))
2016-05-03 12:03:25 +00:00
2016-04-30 15:21:31 +00:00
)
2016-08-11 12:51:05 +00:00
;; ENGINE START !
;; This is function called when running the tool
(defun generate-site()
(if (getf *config* :html)
(create-html-site))
(if (getf *config* :gopher)
(create-gopher-hole)))
2017-12-12 18:37:45 +00:00
2016-04-30 15:21:31 +00:00
(generate-site)
2017-12-12 18:37:45 +00:00
2017-11-28 06:21:33 +00:00
(quit)