4ccfe74a |
(defpackage :alimenta.format
|
bec78605 |
(:use :cl :alexandria :serapeum :fw.lu)
|
a6b78bf0 |
(:export format-document format-title format-link format-paragraph
#:indent-feed))
|
4ccfe74a |
(in-package :alimenta.format)
(defclass document-formatter ()
())
|
bec78605 |
;;; Formatting protocol
;; Generic entrypoint
|
4ccfe74a |
(defgeneric format-document (formatter stream document)
(:documentation "Format a document with the given formatter to the given stream"))
|
bec78605 |
;; Semantic formatters
|
4ccfe74a |
(defgeneric format-title (formatter title)
(:documentation "Format a title according to FORMATTER"))
(defgeneric format-link (formatter link)
(:documentation "Format a link according to FORMATTER"))
(defgeneric format-paragraph (formatter paragraph)
(:documentation "Format a paragraph according to FORMATTER"))
|
bec78605 |
;;; Make alimenta's classes formattable
|
4ccfe74a |
(defmethod format-document (formatter stream (document alimenta::feed-entity))
(format stream "~&~v,4@t~a~%~v,4@t~a~%"
|
dcb293c2 |
(level formatter) (format-title formatter (alimenta:title document))
(level formatter) (format-link formatter (alimenta:link document))))
|
4ccfe74a |
(defmethod format-document (formatter stream (document alimenta:item))
(call-next-method)
|
714b7739 |
(let ((paragraphs (remove-if (op (every #'whitespacep _))
|
dcb293c2 |
(lquery:$ (initialize (alimenta:content document))
(children)
(text)))))
|
714b7739 |
(format stream "~&~{~a~%~}~2&"
|
dcb293c2 |
(map 'list (op (format-paragraph formatter _))
paragraphs))))
|
4ccfe74a |
|
bec78605 |
;;; Define some output formats
|
a6b78bf0 |
;;;; Indentation-based
|
bec78605 |
(defclass indent-formatter (document-formatter)
((%level :initarg :level :accessor level :initform 0)))
(defmethod format-title ((formatter indent-formatter) (title string))
|
168d9e93 |
(format nil "~v,1,0,'*a Title: ~a" (1+ (level formatter)) "" title))
|
bec78605 |
(defmethod format-link ((formatter indent-formatter) (link string))
|
168d9e93 |
(format nil "~vt Link: ~a" (1+ (level formatter)) link))
(defun pp-fill (stream string &optional (colon? t) atsign?)
(declare (ignore atsign?))
(pprint-logical-block (stream (tokens string)
:prefix (if colon? "(" "")
:suffix (if colon? ")" ""))
(pprint-exit-if-list-exhausted)
(loop
(princ (pprint-pop) stream)
(pprint-exit-if-list-exhausted)
(write-char #\space stream)
(pprint-newline :fill stream))))
|
bec78605 |
|
714b7739 |
(defmethod format-paragraph ((formatter indent-formatter) (paragraph string))
|
168d9e93 |
(format nil "~&~v,4@t ~/alimenta.format::pp-fill/~%" (level formatter) paragraph))
|
714b7739 |
|
bec78605 |
(defmethod format-paragraph ((formatter indent-formatter) (paragraph list))
|
714b7739 |
(format nil "~&~{ ~a~%~}" paragraph))
|
bec78605 |
|
4ccfe74a |
(defmethod format-document ((formatter indent-formatter) stream (document alimenta:feed))
(call-next-method)
(incf (level formatter))
(for:for ((item over document))
(format-document formatter stream item)))
|
a6b78bf0 |
;;;; HTML
|
bec78605 |
(defclass html-formatter (document-formatter)
((%level :initarg :level :accessor level :initform 0)))
(defmethod format-title ((formatter html-formatter) (title string))
(format nil "<h~d>~a~2:*</h~d>" (1+ (level formatter)) title))
(defmethod format-link ((formatter html-formatter) (link string))
(format nil "<a href=\"~a\">~:*~a</a>" link))
|
714b7739 |
(defmethod format-paragraph ((formatter html-formatter) (paragraph string))
(format nil "~&<p>~a</p>" paragraph))
|
bec78605 |
(defmethod format-paragraph ((formatter html-formatter) (paragraph list))
(format nil "~%~{<p>~a</p>~}" paragraph))
|
4ccfe74a |
(defmethod format-document ((formatter html-formatter) stream (document alimenta:feed))
(let ((ostream (or stream (make-string-output-stream))))
(unwind-protect
|
dcb293c2 |
(progn (format ostream "~&<html><head><style>main{max-width:40em;margin-left:20em}h1,h2{margin-left:-3em}</style></head><body><main>~%")
(call-next-method formatter ostream document)
(incf (level formatter))
(for:for ((item over document))
(format ostream "~&<article>~%")
(format-document formatter ostream item)
(format ostream "~&</article>~%"))
(format ostream "~&</main></body></html>~%")
(finish-output ostream)
(get-output-stream-string ostream))
|
4ccfe74a |
(unless stream
|
dcb293c2 |
(close ostream)))))
|
a6b78bf0 |
;;;; Org
(defclass org-formatter (document-formatter)
((%level :initarg :level :accessor level :initform 0)))
(defmethod format-document ((formatter org-formatter) stream (document alimenta::feed-entity))
(format stream "~&~a~%~a~2%"
(format-title formatter (alimenta:title document))
(format-link formatter (alimenta:link document))))
(defmethod format-document ((formatter org-formatter) stream (document alimenta:item))
(call-next-method)
(let ((paragraphs (remove-if (op (every #'whitespacep _))
(lquery:$ (initialize (alimenta:content document))
(children)
(text)))))
(format stream "~&~{~a~%~}~2&"
(map 'list (op (format-paragraph formatter _))
paragraphs))))
(defmethod format-title ((formatter org-formatter) (title string))
(with-output-to-string (s)
(loop repeat (1+ (level formatter))
do (princ #\* s))
(princ #\space s)
(princ (serapeum:trim-whitespace title) s)))
(defmethod format-link ((formatter org-formatter) (link string))
(format nil "~vt[[~a]]" (+ 2 (level formatter)) link))
(defmethod format-paragraph ((formatter org-formatter) (paragraph string))
(format nil "~&~v,1@t~/alimenta.format::pp-fill/~%" (+ 2 (level formatter)) paragraph))
(defmethod format-paragraph ((formatter org-formatter) (paragraph list))
(format nil "~&~{ ~a~%~}" paragraph))
(defmethod format-document ((formatter org-formatter) stream (document alimenta:feed))
(call-next-method)
(incf (level formatter))
(for:for ((item over document))
(format-document formatter stream item)))
(defun indent-feed (feed &optional (stream *standard-output*))
(format-document (make-instance 'alimenta.format::indent-formatter)
stream
feed))
|