506 lines
21 KiB
EmacsLisp
506 lines
21 KiB
EmacsLisp
;;; md4tj --- Summary
|
|
|
|
;;; Commentary:
|
|
|
|
;;; Code:
|
|
|
|
(require 'cl-lib)
|
|
(require 'subr-x)
|
|
|
|
;; Add the dash directory to the load-path, since running emacs in batch mode does not define the
|
|
;; load-path
|
|
(add-to-list 'load-path (concat "~/.emacs.d/elpa/" (nth 0 (cl-remove-if-not (lambda (x) (string-match "dash-.*" x)) (directory-files "~/.emacs.d/elpa")))))
|
|
(require 'dash) ;; and then require it
|
|
|
|
;; Basic utilities for subsequent stuff
|
|
(defun md4tj-util-getline ()
|
|
"Get current line from loaded buffer."
|
|
(buffer-substring-no-properties (line-beginning-position) (line-end-position)))
|
|
|
|
(defun md4tj-util-clean-multiline (line)
|
|
"Clean LINE of markdown syntax for ul's, ol's and code's."
|
|
(replace-regexp-in-string
|
|
"^```" "" ;; eliminate the 3 backticks for start of code
|
|
(replace-regexp-in-string
|
|
"^[0-9]+\\. " "" ;; eliminate the numbering for ordered lists
|
|
(replace-regexp-in-string
|
|
"^- " "" ;; eliminate the dashes for unordered lists
|
|
(replace-regexp-in-string
|
|
"^>" "" ;; eliminate the right-caret for quotes
|
|
line)))))
|
|
|
|
(defun md4tj-util-escape-chars (line)
|
|
"Escape characters in LINE that would be misinterpreted by the browser."
|
|
(string-replace
|
|
"<" "<"
|
|
(string-replace ">" ">" line)))
|
|
|
|
(defun md4tj-util-zip (ls)
|
|
"Turn list of lists LS into list of tuples."
|
|
(cl-labels ((zip-help (ll) (if (not (car ll)) nil (cons (cl-map 'listp #'car ll) (zip-help (cl-map 'listp 'cdr ll))))))
|
|
(zip-help ls)))
|
|
;; End basic utilities
|
|
|
|
;; Normal md4tj stuff
|
|
(defun md4tj-begin-tag (tag &optional attrs)
|
|
"Return beginning html tag for TAG with optional ATTRS."
|
|
(concat "<" tag
|
|
(mapconcat 'identity (cl-map 'listp (lambda (l) (concat " " (nth 0 l) "=" "\"" (nth 1 l) "\"")) attrs) "")
|
|
">"))
|
|
|
|
(defun md4tj-end-tag (tag)
|
|
"Return end html tag for TAG."
|
|
(concat "</" tag ">"))
|
|
|
|
(defun md4tj-process-header (line)
|
|
"Process LINE known to be header, return HTML."
|
|
(let ((level (length (nth 0 (split-string line)))))
|
|
(if (or (< level 0) (> level 6))
|
|
(error (concat "Error parsing: " line "\n"))
|
|
(concat (md4tj-begin-tag (concat "h" (number-to-string level)))
|
|
(mapconcat 'identity (cdr (split-string line)) " ")
|
|
(md4tj-end-tag (concat "h" (number-to-string level)))))))
|
|
|
|
(defun md4tj-process-paragraph (line)
|
|
"Process LINE that is paragraph, return HTML."
|
|
(concat (md4tj-begin-tag "p") line (md4tj-end-tag "p")))
|
|
|
|
(defun md4tj-process-line (line)
|
|
"Process all inline elements of the LINE, return HTML."
|
|
;; Finally strikethrough
|
|
(replace-regexp-in-string
|
|
"~~\\(.*?\\)~~"
|
|
"<s>\\1</s>"
|
|
;; Then highlight
|
|
(replace-regexp-in-string
|
|
"==\\(.*?\\)=="
|
|
"<mark>\\1</mark>"
|
|
;; Then links
|
|
(replace-regexp-in-string
|
|
"\\[\\([^\\[]*\\)\\](\\([^ ]*\\))"
|
|
(concat (md4tj-begin-tag "a" (list '("href" "\\2"))) "\\1" (md4tj-end-tag "a"))
|
|
;; Then images
|
|
(replace-regexp-in-string
|
|
"!\\[\\([^\\[]*\\)](\\([^\\[(]*\\))"
|
|
(md4tj-begin-tag "img" (list '("src" "\\2") '("alt" "\\1")))
|
|
;; Then videos
|
|
(replace-regexp-in-string
|
|
"!!\\[\\([^\\[]*\\)](\\([^\\[(]*\\))"
|
|
(concat (md4tj-begin-tag "video" (list '("src" "\\2") '("type" "video/webm") '("controls" "true"))) "\\1" (md4tj-end-tag "video"))
|
|
;; Then emphasis
|
|
(replace-regexp-in-string
|
|
"\\*\\(.*+\\)\\*"
|
|
"<em>\\1</em>"
|
|
;; Then strong
|
|
(replace-regexp-in-string
|
|
"\\*\\*\\(.*+\\)\\*\\*"
|
|
"<strong>\\1</strong>"
|
|
;; First code
|
|
(replace-regexp-in-string
|
|
"`\\(.*\\)`"
|
|
"<code>\\1</code>" line)))))))))
|
|
|
|
(defun md4tj-handle-table-row (line header)
|
|
"Return the string for a table row that is a HEADER from LINE."
|
|
(let ((opentag (if header "<th>" "<td>"))
|
|
(closetag (if header "</th>" "</td>")))
|
|
(if (string-match "^|---.*" line) "" ;; do nothing if the line under the header for the table
|
|
(concat "<tr>\n" (mapconcat (lambda (x) (concat opentag (md4tj-process-line x) closetag "\n")) (split-string line "|" t " *")) "</tr>"))))
|
|
|
|
(defun md4tj-convert-line-to-html (line state inbuf outfile)
|
|
"Process LINE with STATE and return html, INBUF provided with OUTFILE."
|
|
(let ((cleanline (md4tj-util-clean-multiline line)))
|
|
;; If this is a signal to include another file
|
|
(cond ((string-match "^@@INCLUDE" line) (md4tj-parse-to-string (nth 1 (split-string line))))
|
|
((string-match "^@@LASTUPDATED" line) (concat
|
|
(md4tj-begin-tag "p" (list (list "id" "lastupdated")))
|
|
"Last updated: " (format-time-string "%a %d %b %Y %H:%M UTC" nil t)
|
|
(md4tj-end-tag "p")))
|
|
((string-match "^@@DIV" line) (md4tj-begin-tag "div" (list (list "class" (nth 1 (split-string line))))))
|
|
((string-match "^@@ENDDIV" line) (concat (mapconcat #'md4tj-state-to-html state "\n") (md4tj-end-tag "div")))
|
|
((string-match "^@@BLOGINSERT" line) (with-current-buffer inbuf (md4tj-blog-html)))
|
|
((string-match "^@@RSSINSERT" line) (md4tj-rss-link-string (concat (with-current-buffer inbuf (save-excursion (md4tj-blog-base-url))) "feed.xml")))
|
|
((and (string-match "^$$.*$$" line) (eq (nth 1 state) 'normal)) ;; LaTeX formula
|
|
(shell-command (concat "./pnglatex"
|
|
" -d 300"
|
|
" -f "
|
|
"\""
|
|
(string-replace "$$" "" line)
|
|
"\""
|
|
" -o "
|
|
"./teximg/"
|
|
(md5 (string-replace "$$" "" line)) ".png"))
|
|
(md4tj-begin-tag "img" (list (list "class" "teximg") ;; give teximgs their own class
|
|
(list "src" (concat "./teximg/" (md5 (string-replace "$$" "" line)) ".png"))
|
|
(list "alt" (string-replace "$$" "" line)))))
|
|
;; If this is some other signal, ignore
|
|
((string-match "^@@" line) "")
|
|
;; Otherwise, process as normal
|
|
(t
|
|
(concat
|
|
;; Beginning multiline block/ending prev multiline block
|
|
(mapconcat #'md4tj-state-to-html state "\n")
|
|
|
|
;; Body
|
|
(cond ((or (eq (nth 1 state) 'code) (eq (nth 1 state) 'begincode)) (md4tj-util-escape-chars cleanline))
|
|
((string-match "^#+ " cleanline) (md4tj-process-header (md4tj-process-line cleanline)))
|
|
((string= "---" cleanline) "<hr>") ;; horizontal line
|
|
((= (length cleanline) 0) "<br>") ;; blank line
|
|
((eq (nth 1 state) 'begintable) (md4tj-handle-table-row cleanline t))
|
|
((eq (nth 1 state) 'table) (md4tj-handle-table-row cleanline nil))
|
|
(t (md4tj-process-paragraph (md4tj-process-line cleanline))))
|
|
|
|
;; End of multiline block
|
|
(cond ((or (eq (nth 1 state) 'ul) (eq (nth 1 state) 'beginul)) "</li>")
|
|
((or (eq (nth 1 state) 'ol) (eq (nth 1 state) 'beginol)) "</li>")
|
|
((eq (nth 1 state) 'code) "")
|
|
(t "")))))))
|
|
|
|
(defun md4tj-state-to-html (state)
|
|
"Convert STATE to html."
|
|
(cond ((eq state 'beginul) "<ul>\n<li>")
|
|
((eq state 'beginol) "<ol>\n<li>")
|
|
((eq state 'begincode) "<pre>\n<code>")
|
|
((eq state 'ul) "<li>")
|
|
((eq state 'ol) "<li>")
|
|
((eq state 'code) "")
|
|
((eq state 'endul) "</ul>\n")
|
|
((eq state 'endol) "</ol>\n")
|
|
((eq state 'endcode) "</code>\n</pre>\n")
|
|
((eq state 'begintable) "<table>") ;; might need to add to this?
|
|
((eq state 'endtable) "</table>")
|
|
((eq state 'beginquote) "<blockquote>")
|
|
((eq state 'endquote) "</blockquote>")
|
|
(t "")))
|
|
|
|
(defun md4tj-next-state (currline prevstate)
|
|
"Return the state based on CURRLINE and PREVSTATE."
|
|
(list
|
|
;; End state
|
|
(cond ((and (string-match "^- " currline) (or (eq prevstate 'ol) (eq prevstate 'beginol))) 'endol)
|
|
((and (string-match "^[0-9]+\\. " currline) (or (eq prevstate 'ul) (eq prevstate 'beginul))) 'endul)
|
|
((and (not (string-match "^- " currline)) (or (eq prevstate 'beginul) (eq prevstate 'ul))) 'endul)
|
|
((and (not (string-match "[0-9]+\\. " currline)) (or (eq prevstate 'beginol) (eq prevstate 'ol))) 'endol)
|
|
((and (string-match "```$" currline) (or (eq prevstate 'code) (eq prevstate 'begincode))) 'endcode)
|
|
((and (not (string-match "^|.*|$" currline)) (or (eq prevstate 'begintable) (eq prevstate 'headtable) (eq prevstate 'table))) 'endtable)
|
|
((and (not (string-match "^>.*" currline)) (or (eq prevstate 'beginquote) (eq prevstate 'quote))) 'endquote)
|
|
(t 'nothing))
|
|
|
|
;; Begin state (or next line's prevstate)
|
|
(cond ((and (string-match "^- " currline) (not (or (eq prevstate 'beginul) (eq prevstate 'ul))) 'beginul))
|
|
((and (string-match "^- " currline) (or (eq prevstate 'beginul) (eq prevstate 'ul)) 'ul))
|
|
((and (string-match "^[0-9]+\\. " currline) (not (or (eq prevstate 'beginol) (eq prevstate 'ol))) 'beginol))
|
|
((and (string-match "^[0-9]+\\. " currline) (or (eq prevstate 'beginol) (eq prevstate 'ol)) 'ol))
|
|
((and (string-match "^```" currline) (not (or (eq prevstate 'begincode) (eq prevstate 'code))) 'begincode))
|
|
((and (not (string-match "```$" currline)) (or (eq prevstate 'begincode) (eq prevstate 'code)) 'code))
|
|
((and (string-match "^|.*|$" currline) (not (or (eq prevstate 'table) (eq prevstate 'begintable)))) 'begintable)
|
|
((and (string-match "^|.*|$" currline) (or (eq prevstate 'begintable) (eq prevstate 'table))) 'table)
|
|
((and (string-match "^>.*" currline) (not (or (eq prevstate 'quote) (eq prevstate 'beginquote)))) 'beginquote)
|
|
((and (string-match "^>.*" currline) (or (eq prevstate 'beginquote) (eq prevstate 'quote))) 'quote)
|
|
(t 'normal))))
|
|
|
|
(defun md4tj-begin ()
|
|
"Insert beginning code for all html, initialize."
|
|
(shell-command "mkdir -p ./teximg") ;; Ensure ./teximg folder exists
|
|
(concat "<!DOCTYPE html>\n"
|
|
(md4tj-begin-tag "html"
|
|
(list (list "lang"
|
|
(or (nth 1 (car (cl-remove-if-not (lambda (meta) (string= (car meta) "@@LANG")) (md4tj-find-metas)))) "en"))))
|
|
"\n"))
|
|
|
|
(defun md4tj-finalize (state)
|
|
"Finalizes HTML document by inserting missing end tags based on STATE."
|
|
(concat
|
|
(cond ((or (eq state 'beginul) (eq state 'ul)) "</ul>")
|
|
((or (eq state 'beginol) (eq state 'ol)) "</ol>")
|
|
((or (eq state 'begincode) (eq state 'code)) "</code>")
|
|
((or (eq state 'begintable) (eq state 'table)) "</table>")
|
|
((or (eq state 'beginquote) (eq state 'quote)) "</blockquote>")
|
|
(t ""))
|
|
"</body>\n"
|
|
"</html>"))
|
|
|
|
(defun md4tj-find-metas ()
|
|
"Return all lines starting with @@ as list of split strings."
|
|
(save-excursion
|
|
(let ((meta-list nil))
|
|
(goto-char (point-min))
|
|
(while (re-search-forward "^@@" nil t)
|
|
(setq meta-list (cons (split-string (md4tj-util-getline)) meta-list)))
|
|
meta-list)))
|
|
|
|
(defun md4tj-list-to-tuple-list (list)
|
|
"Convert LIST to tuple list."
|
|
(let ((tuples nil))
|
|
(dolist (i (number-sequence 0 (- (length list) 1) 2))
|
|
(setq tuples (cons (cons (nth i list) (list (nth (+ i 1) list))) tuples)))
|
|
(reverse tuples)))
|
|
|
|
(defun md4tj-meta-to-html (meta)
|
|
"Convert META to html."
|
|
(cond ((string= (nth 0 meta) "@@TITLE")
|
|
(concat "<title>" (mapconcat 'identity (cdr meta) " ") "</title>\n"))
|
|
((string= (nth 0 meta) "@@META")
|
|
(concat (md4tj-begin-tag "meta" (md4tj-list-to-tuple-list (cdr meta)))))
|
|
((string= (nth 0 meta) "@@CSS")
|
|
(concat (md4tj-begin-tag "link" (list (list "rel" "stylesheet") (list "href" (nth 1 meta))))))))
|
|
|
|
(defun md4tj-head ()
|
|
"Return text for head element on current buffer."
|
|
(concat
|
|
(md4tj-begin-tag "head") "\n"
|
|
(mapconcat 'md4tj-meta-to-html (md4tj-find-metas) "\n")
|
|
"\n"
|
|
(md4tj-end-tag "head") "\n"))
|
|
|
|
(defun md4tj-parse (mdfile outfile)
|
|
"Entry point to parse MDFILE and output to OUTFILE."
|
|
(let ((inbuf (generate-new-buffer " in"))
|
|
(outbuf (generate-new-buffer " out"))
|
|
(fullstate (list 'nothing 'normal))
|
|
(line nil))
|
|
(set-buffer inbuf)
|
|
(insert-file-contents mdfile)
|
|
(goto-char (point-min))
|
|
;; If we need RSS, go ahead and output the XML file here
|
|
(if (md4tj-rss-needs-rss) (md4tj-rss-to-file mdfile (md4tj-rss-file-from-outfile outfile (with-temp-buffer (insert-file-contents mdfile) (md4tj-blog-base-dir)))))
|
|
(set-buffer outbuf)
|
|
(insert (md4tj-begin))
|
|
(insert (with-current-buffer inbuf (md4tj-head)))
|
|
(insert (md4tj-begin-tag "body"))
|
|
(while (with-current-buffer inbuf (< (point) (point-max)))
|
|
(setq line (with-current-buffer inbuf (md4tj-util-getline)))
|
|
(setq fullstate (md4tj-next-state line (nth 1 fullstate)))
|
|
;; Insert next line(s) into output file
|
|
(let ((linehtml (md4tj-convert-line-to-html line fullstate inbuf outfile)))
|
|
(insert (concat linehtml (if (string-empty-p linehtml) "" "\n"))))
|
|
;; Advance input file by a line
|
|
(with-current-buffer inbuf (forward-line)))
|
|
(insert (md4tj-finalize (nth 1 fullstate)))
|
|
;; Write outbuf to outfile
|
|
(write-region nil nil outfile nil)))
|
|
|
|
;; NOTE: Cannot md4tj-parse-to-string a file with a blog
|
|
;; This is fine, as included files are mostly meant to be stuff like
|
|
;; navbars, etc.
|
|
(defun md4tj-parse-to-string (mdfile)
|
|
"Parse MDFILE, return conversion to HTML as string."
|
|
(with-temp-buffer
|
|
(let ((acc nil)
|
|
(line nil)
|
|
(fullstate (list 'nothing 'normal)))
|
|
(insert-file-contents mdfile)
|
|
(goto-char (point-min))
|
|
(while (< (point) (point-max))
|
|
(setq line (md4tj-util-getline))
|
|
(setq fullstate (md4tj-next-state line (nth 1 fullstate)))
|
|
(setq acc (concat acc (md4tj-convert-line-to-html line fullstate nil nil) "\n"))
|
|
(forward-line))
|
|
acc)))
|
|
|
|
;; End normal md4tj stuff
|
|
|
|
;; md4tj blog stuff
|
|
(defun md4tj-blog-base-url ()
|
|
"Find base URL of blog."
|
|
(save-excursion
|
|
(goto-char (point-min))
|
|
(if (search-forward-regexp "^@@BLOGBASEURL" (point-max) t)
|
|
(replace-regexp-in-string "^@@BLOGBASEURL[ \\\t]+" "" (md4tj-util-getline))
|
|
nil)))
|
|
|
|
(defun md4tj-blog-base-dir ()
|
|
"Find base directory of blog."
|
|
(save-excursion
|
|
(goto-char (point-min))
|
|
(if (search-forward-regexp "^@@BLOGBASEDIR" (point-max) t)
|
|
(let ((tmp (replace-regexp-in-string "^@@BLOGBASEDIR[ \\\t]+" "" (md4tj-util-getline))))
|
|
(if (string-match "/$" tmp) tmp (concat tmp "/"))))))
|
|
|
|
;; (defun md4tj-blog-names ()
|
|
;; "Find all blogs on blog page."
|
|
;; (cl-map #'listp (lambda (l) (nth 1 l)) (cl-map #'listp 'split-string (cl-remove-if-not (lambda (s) (string-match "^@@BLOGPOST" s)) (split-string (buffer-string) "\n")))))
|
|
(defun md4tj-blog-names ()
|
|
"Get all blogs names from buffer."
|
|
(cl-map 'listp (lambda (s) (string-replace (md4tj-blog-base-dir) "" s)) (cl-map 'listp (lambda (s) (replace-regexp-in-string "\\.md4tj$" "" s)) (md4tj-blog-files))))
|
|
|
|
(defun md4tj-blog-links ()
|
|
"Get all links on the blog page."
|
|
(let ((base-url (md4tj-blog-base-url)))
|
|
(cl-map #'listp (lambda (s) (concat base-url s ".html")) (md4tj-blog-names))))
|
|
|
|
(defun md4tj-blog-file (name)
|
|
"Get full filename from NAME."
|
|
(concat (md4tj-blog-base-dir) name ".md4tj"))
|
|
|
|
(defun md4tj-blog-files ()
|
|
"Get all blog .md4tj files."
|
|
(cl-map 'listp (lambda (s) (concat (md4tj-blog-base-dir) s)) (cl-remove-if-not (lambda (s) (string-match ".*\\.md4tj$" s)) (directory-files (md4tj-blog-base-dir)))))
|
|
;(cl-map #'listp 'md4tj-blog-file (md4tj-blog-names)))
|
|
|
|
(defun md4tj-blog-title (f)
|
|
"Get blog title from file F."
|
|
(with-temp-buffer
|
|
(insert-file-contents f)
|
|
(goto-char (point-min))
|
|
(if (search-forward-regexp "^@@BLOGPOSTTITLE" (point-max) t)
|
|
(replace-regexp-in-string "^@@BLOGPOSTTITLE[ \t]+" "" (md4tj-util-getline))
|
|
"")))
|
|
|
|
(defun md4tj-blog-titles ()
|
|
"Get all blog titles in current file."
|
|
(cl-map #'listp 'md4tj-blog-title (md4tj-blog-files)))
|
|
|
|
(defun md4tj-blog-time (f)
|
|
"Get blog time (unix time) from file F."
|
|
(with-temp-buffer
|
|
(insert-file-contents f)
|
|
(goto-char (point-min))
|
|
(if (search-forward-regexp "^@@BLOGPOSTDATE" (point-max) t)
|
|
(replace-regexp-in-string "^@@BLOGPOSTDATE[ \t]+" "" (md4tj-util-getline))
|
|
"")))
|
|
|
|
(defun md4tj-blog-times ()
|
|
"Get all blog times in current file."
|
|
(cl-map #'listp 'md4tj-blog-time (md4tj-blog-files)))
|
|
|
|
|
|
;; triple of
|
|
;; Title
|
|
;; Time
|
|
;; Link
|
|
(defun md4tj-blog-all-blogs-list ()
|
|
"Get all blogs in current buffer."
|
|
(-sort (lambda (l1 l2) (> (string-to-number (nth 1 l1)) (string-to-number (nth 1 l2)))) (md4tj-util-zip (list (md4tj-blog-titles) (md4tj-blog-times) (md4tj-blog-links) (md4tj-blog-files)))))
|
|
|
|
(defun md4tj-blog-all-blogs-list-elt-to-html (elt)
|
|
"Convert abl list elt ELT to html."
|
|
(concat "<div class=blogpost>" "\n"
|
|
"<a href=\"" (nth 2 elt) "\"><h4>" (nth 0 elt) "</h4></a>" "\n"
|
|
"<h5>" (format-time-string "%Y %B %d %H:%M UTC" (string-to-number (nth 1 elt)) t) "</h5>" "\n"
|
|
"</div>"))
|
|
|
|
(defun md4tj-blog-all-blogs-list-to-html (abl)
|
|
"Convert all blogs list ABL to html."
|
|
(mapconcat 'md4tj-blog-all-blogs-list-elt-to-html (md4tj-blog-all-blogs-list) "\n"))
|
|
|
|
(defun md4tj-blog-html ()
|
|
"Return blog html."
|
|
(save-excursion
|
|
(goto-char (point-min))
|
|
(md4tj-blog-all-blogs-list-to-html (md4tj-blog-all-blogs-list))))
|
|
|
|
;; End md4tj blog stuff
|
|
|
|
;; md4tj rss stuff
|
|
|
|
(defun md4tj-rss-escape-string-for-rss (str)
|
|
"Escape STR for RSS."
|
|
(replace-regexp-in-string "'" "'"
|
|
(replace-regexp-in-string "\"" """
|
|
(replace-regexp-in-string ">" ">"
|
|
(replace-regexp-in-string "<" "<"
|
|
(replace-regexp-in-string "&" "&" str))))))
|
|
|
|
(defun md4tj-rss-needs-rss ()
|
|
"Scans current buffer, returning t if needing rss."
|
|
(save-excursion
|
|
(goto-char (point-min))
|
|
(if (search-forward-regexp "^@@RSSENABLE" nil t) t nil)))
|
|
|
|
(defun md4tj-rss-begin-tag (tag &optional attrs self-close)
|
|
"Return the RSS text for TAG with ATTRS, SELF-CLOSE if necessary."
|
|
(concat "<" tag (mapconcat (lambda (attr) (concat " " (nth 0 attr) "=" "\"" (nth 1 attr) "\"")) attrs "") (if self-close "/" "") ">"))
|
|
|
|
(defun md4tj-rss-end-tag (tag)
|
|
"Return the RSS text for ending TAG."
|
|
(concat "</" tag ">"))
|
|
|
|
(defun md4tj-rss-begin ()
|
|
"Return the RSS text to begin RSS document."
|
|
(md4tj-rss-begin-tag "rss" (list (list "version" "2.0"))))
|
|
|
|
(defun md4tj-rss-is-valid-channel-statement (toks)
|
|
"Return t if the car of TOKS is a valid channel statement."
|
|
;; Note: user isn't allowed to set docs, generator, or lastBuildDate tags
|
|
(member (car toks) (list "title" "link" "description" "language" "copyright" "managingEditor" "webMaster" "pubDate" "category" "ttl" "rating")))
|
|
|
|
(defun md4tj-rss-get-channel-statements ()
|
|
"Get all channel statements from current buffer."
|
|
(cl-remove-if-not #'md4tj-rss-is-valid-channel-statement (cl-map 'listp (lambda (toks) (cons (string-replace "@@RSSCHANNEL" "" (car toks)) (cdr toks))) (cl-map 'listp #'split-string (cl-remove-if-not (lambda (line) (string-match "^@@RSSCHANNEL.*" line)) (split-string (buffer-string) "\n"))))))
|
|
|
|
(defun md4tj-rss-channel-statement-toks-to-rss (toks)
|
|
"Generate RSS syntax for RSS channel statement tokenized to TOKS."
|
|
(concat (md4tj-rss-begin-tag (car toks)) (mapconcat #'identity (cdr toks) " ") (md4tj-rss-end-tag (car toks))))
|
|
|
|
(defun md4tj-rss-channel (rsslink)
|
|
"Return the RSS text for channel in current buffer with RSSLINK."
|
|
(concat (md4tj-rss-begin-tag "channel")
|
|
;;(md4tj-rss-begin-tag "atom:link" (list (list "href" rsslink) (list "rel" "self") (list "type" "application/rss+xml")) t)
|
|
(mapconcat #'md4tj-rss-channel-statement-toks-to-rss (md4tj-rss-get-channel-statements) "")
|
|
(md4tj-rss-begin-tag "docs") "https://www.rssboard.org/rss-specification" (md4tj-rss-end-tag "docs")
|
|
(md4tj-rss-begin-tag "generator") "md4tj-rss.el" (md4tj-rss-end-tag "generator")
|
|
(md4tj-rss-begin-tag "lastBuildDate") (format-time-string "%a, %d %b %Y %H:%M:%S GMT") (md4tj-rss-end-tag "lastBuildDate")))
|
|
|
|
;; eltfilelist is a tuple of
|
|
;; File name
|
|
;; Elt, where
|
|
;; Elt is triple of
|
|
;; Title
|
|
;; Time
|
|
;; Link
|
|
;; This is really messy but the easiest way to get the filename into
|
|
;; md4tj-rss-item in order to be able to provide the entire blog post
|
|
;; in the RSS feed
|
|
(defun md4tj-rss-item (elt)
|
|
"Return RSS for ELT."
|
|
(concat (md4tj-rss-begin-tag "item")
|
|
(md4tj-rss-begin-tag "title") (nth 0 elt) (md4tj-rss-end-tag "title")
|
|
(md4tj-rss-begin-tag "link") (nth 2 elt) (md4tj-rss-end-tag "link")
|
|
(md4tj-rss-begin-tag "guid") (nth 2 elt) (md4tj-rss-end-tag "guid")
|
|
(md4tj-rss-begin-tag "description") (md4tj-rss-escape-string-for-rss (md4tj-parse-to-string (nth 3 elt))) (md4tj-rss-end-tag "description")
|
|
(md4tj-rss-begin-tag "pubDate") (format-time-string "%a, %d %b %Y %H:%M:%S %Z" (string-to-number (nth 1 elt))) (md4tj-rss-end-tag "pubDate")
|
|
(md4tj-rss-end-tag "item")))
|
|
|
|
(defun md4tj-rss-items ()
|
|
"Return all RSS for all elements."
|
|
(mapconcat 'md4tj-rss-item (md4tj-blog-all-blogs-list) ""))
|
|
|
|
|
|
|
|
(defun md4tj-rss (rsslink)
|
|
"Return the RSS text for the current buffer at RSSLINK."
|
|
(save-excursion
|
|
(when (md4tj-rss-needs-rss)
|
|
(concat
|
|
(md4tj-rss-begin)
|
|
(md4tj-rss-channel rsslink)
|
|
(md4tj-rss-items)
|
|
(md4tj-rss-end-tag "channel")
|
|
(md4tj-rss-end-tag "rss")))))
|
|
|
|
(defun md4tj-rss-to-file (infile outfile)
|
|
"Write INFILE as RSS to OUTFILE."
|
|
(with-temp-buffer
|
|
(insert-file-contents infile)
|
|
(let ((outbuf (generate-new-buffer " outbuf" t))
|
|
(rss-str (md4tj-rss (concat (md4tj-blog-base-url) "/feed.xml"))))
|
|
(set-buffer outbuf)
|
|
(insert rss-str)
|
|
(write-region nil nil outfile nil))))
|
|
|
|
(defun md4tj-rss-link-string (rsslink)
|
|
"Return the html of RSS link to RSSLINK as string."
|
|
(concat (md4tj-begin-tag "div" (list (list "id" "rssicon"))) (md4tj-begin-tag "a" (list (list "href" rsslink))) (md4tj-begin-tag "img" (list (list "src" "img/rss.png") (list "alt" "RSS icon") (list "style" "width:20px;height:20px;"))) (md4tj-end-tag "img") (md4tj-end-tag "a") (md4tj-end-tag "div")))
|
|
|
|
(defun md4tj-rss-file-from-outfile (outfile subdir)
|
|
"Return filename RSS XML should be output to given OUTFILE in SUBDIR."
|
|
(concat (file-name-directory outfile) "/" subdir "/" "feed.xml"))
|
|
|
|
|
|
|
|
;; End md4tj rss stuff
|
|
|
|
(provide 'md4tj)
|
|
;;; md4tj.el ends here
|