git.fiddlerwoaroof.com
Browse code

variou structural changes

fiddlerwoaroof authored on 19/07/2016 08:49:26
Showing 6 changed files
... ...
@@ -3,8 +3,18 @@ A library for handling RSS/Atom feeds
3 3
 The library implements most of the elements in the RSS and Atom specs and handles feed discovery, feed type
4 4
 detection and feed pulling as well as feed generation.  It is also fairly easy to extend by subclassing the
5 5
 appropriate classes and specializing the generic functions `%to-feed`, `make-item`, `%get-items` and
6
-`%generate-xml`.
6
+`generate-xml`.
7 7
 
8 8
 It is, however, noticeably incomplete: particularly, not all the useful functions have been exported from the
9
-packages they reside in.  Also, it relies on code in my fork of chronicity <http://github.com/fiddlerwoaroof/chronicity>
10
-to handle certain wordpress feeds.
9
+packages they reside in.  Also, it relies on code in my fork of chronicity
10
+<http://github.com/fiddlerwoaroof/chronicity> to handle certain wordpress feeds.
11
+
12
+There are a couple additional features thrown in:
13
+
14
+- server-test.lisp runs a demo web server that pulls a bunch of feeds from reddit and serves them up as a
15
+  single page website.  It's main reason for existence is to test this library and the library I've been
16
+  working on for web stuff <http://github.com/fiddlerwoaroof/araneus>
17
+
18
+- alimenta-clim.lisp runs a demo graphical application that uses McCLIM
19
+  <https://github.com/robert-strandh/mcclim> to generate an X application that can be used to navigate an RSS
20
+  feed.
... ...
@@ -8,6 +8,7 @@
8 8
                #:lquery
9 9
                #:should-test
10 10
                #:alexandria
11
+               #:serapeum
11 12
                #:anaphora
12 13
                #:chronicity
13 14
                #:fwoar.lisputils
... ...
@@ -1,39 +1,39 @@
1 1
 ;;;; alimenta.lisp
2
-(declaim (optimize (speed 0) (safety 3) (debug 3)))
2
+(declaim (optimize (speed 0) (safety 3) (debug 4)))
3 3
 
4 4
 (in-package #:alimenta)
5 5
 
6
+(defgeneric -to-feed (doc type &key feed-link)
7
+  (:documentation "Given an xml-document, return a feed object"))
8
+
9
+(defgeneric generate-xml (feed feed-type &key partial)
10
+  (:documentation "Given a lisp object representing a feed, return an xml
11
+                   document"))
12
+
6 13
 (defclass feed () 
7
-  ((title :initarg :title :initform nil)
8
-   (link :initarg :link :initform nil)
14
+  ((description :initarg :description :initform nil :accessor description)
15
+   (doc :initarg :doc :initform nil :accessor doc)
16
+   (feed-link :initarg :feed-link :initform nil :accessor feed-link)
9 17
    (items :initarg :items :initform nil :accessor items)
10
-   (description :initarg :description :initform nil)
11
-   (feed-link :initarg :feed-link :initform nil)
12
-   (doc :initarg :doc :initform nil)
13
-   (source-type :initarg :source-type :initform nil)))
18
+   (link :initarg :link :initform nil :accessor link)
19
+   (source-type :initarg :source-type :initform nil :accessor source-type)
20
+   (title :initarg :title :initform nil :accessor title)))
14 21
 
15 22
 (defclass item ()
16
-  ((title :initarg :title :initform nil)
17
-   (id :initarg :id :initform nil)
18
-   (author :initarg :author :initform nil)
19
-   (date :initarg :date :initform nil)
20
-   (link :initarg :link :initform nil)
21
-   (links :initform (make-hash-table :test #'equalp))
23
+  ((author :initarg :author :initform nil :accessor author)
22 24
    (content :initarg :content :initform nil :accessor content)
23
-   (doc :initarg :doc :initform nil)))
25
+   (date :initarg :date :initform nil :accessor date)
26
+   (doc :initarg :doc :initform nil :accessor doc)
27
+   (id :initarg :id :initform nil :accessor id)
28
+   (link :initarg :link :initform nil :accessor link)
29
+   (links :initform (make-hash-table :test #'equalp) :accessor links)
30
+   (title :initarg :title :initform nil :accessor title)))
24 31
 
25 32
 (defclass complex-value () ())
26 33
 
27 34
 (defgeneric primary-value (self)
28
-  (:documentation "Primarily for COMPLEX-VALUES: this should take one and return a useful primary value")
29
-  (:method ((self t)) self))
30
-
31
-(define-condition duplicate-link-type (error)
32
-  ((old :reader duplicate-link-type-old :initarg :old)
33
-   (new :reader duplicate-link-type-new :initarg :new))
34
-  (:report (lambda (condition stream)
35
-             (format stream "Item already has link ~s" (duplicate-link-type-old condition)))))
36
-
35
+  (:documentation "Primarily for COMPLEX-VALUES: this should take one and
36
+                   return a useful primary value"))
37 37
 
38 38
 (defgeneric push-item (feed item)
39 39
   (:documentation "Adds an item to the feed"))
... ...
@@ -41,31 +41,40 @@
41 41
 (defgeneric make-item (xml-dom doc-type)
42 42
   (:documentation "Given an xml document, return an item"))
43 43
 
44
-(defgeneric parse-feed (feed))
44
+(defgeneric parse-feed (feed)
45
+  (:documentation "Parse a feed into a lisp object"))
46
+
47
+(defgeneric get-items (xml feed-type)
48
+  (:documentation "Given an xml document, extract its items"))
45 49
 
46
-(defgeneric %get-items (xml feed-type))
50
+(defmethod primary-value ((self t))
51
+  self)
47 52
 
48
-(defgeneric %generate-xml (feed feed-type &key partial))
49
-(defmethod %generate-xml :around ((feed feed) feed-type &rest r)
53
+(define-condition duplicate-link-type (error)
54
+  ((old :reader duplicate-link-type-old :initarg :old)
55
+   (new :reader duplicate-link-type-new :initarg :new))
56
+  (:report (lambda (condition stream)
57
+             (format stream "Item already has link ~s" (duplicate-link-type-old condition)))))
58
+
59
+
60
+(defmethod generate-xml :around ((feed feed) feed-type &rest r)
50 61
   (declare (ignore r))
51 62
   (let ((result (call-next-method feed feed-type)))
52 63
     (with-slots (items) feed
53 64
       (loop for item in items
54
-            do (%generate-xml item feed-type :partial result)))
65
+            do (generate-xml item feed-type :partial result)))
55 66
     result))
56 67
 
57
-(defgeneric %to-feed (doc type &key feed-link)
58
-  (:documentation "Given an xml-document, return a feed object"))
59
-(defmethod %to-feed :around ((xml-dom plump:node) doc-type &key feed-link)
60
-  "This wraps the particular methods so that _they_ don't have to implement item fetching.
61
-   NIL passed to the type activates auto-detection"
68
+(defmethod -to-feed :around ((xml-dom plump:node) doc-type &key feed-link)
69
+  "This wraps the particular methods so that _they_ don't have to implement
70
+   item fetching.  NIL passed to the type activates auto-detection"
62 71
   (aprog1 (call-next-method xml-dom doc-type :feed-link feed-link)
63 72
     (with-slots (doc source-type) it
64 73
       (setf doc xml-dom
65 74
             source-type doc-type))
66 75
     (with-slots (items) it
67 76
       (setf
68
-        items (loop for item across (%get-items xml-dom doc-type)
77
+        items (loop for item across (get-items xml-dom doc-type)
69 78
                     collect (make-item item doc-type))))))
70 79
 
71 80
 (defgeneric (setf link) (value self))
... ...
@@ -109,50 +118,31 @@
109 118
             (t root-node-name)))))
110 119
 
111 120
 
112
-(defun generate-xml (feed &key (feed-type :rss))
113
-  (%generate-xml feed feed-type))
121
+;(defun generate-xml (feed &key (feed-type :rss))
122
+;  (%generate-xml feed feed-type))
114 123
 
115 124
 (defun to-feed (doc &key type feed-link)
116
-  "Makes an instance of feed from the given document.  Specialize %to-feed with
125
+  "Makes an instance of feed from the given document.  Specialize to-feed with
117 126
    an equal-specializer on type with an appropriate symbol to implement a new
118 127
    sort of feed."
119 128
   (unless type
120 129
     (setf type (detect-feed-type doc)))
121
-  (%to-feed doc type :feed-link feed-link))
122
-
123
-
124
-(defun get-items (feed xml-dom &key type)
125
-  (with-slots (items) feed
126
-    (loop for item across (%get-items xml-dom type)
127
-          do (push (make-item xml-dom type) items)
128
-          finally (return items)))) 
129
-
130
-(defgeneric feed-to-rss (feed))
131
-(defgeneric feed-to-atom (feed))
132
-(defgeneric feed-to-json (feed))
133
-(defgeneric feed-to-html5 (feed)
134
-  (:documentation
135
-    "take a feed object, produce an html5 output.  Simple format:
136
-     <!DOCTYPE html>
137
-     <html lang=\"en\">
138
-     <head>
139
-       <meta charset=\"UTF-8\">
140
-       <title>Feed Title</title>
141
-     </head>
142
-     <body>
143
-       <main>
144
-         <article id=\"id\">
145
-           <h1>Title</h1>
146
-           <h2>Author</h2>
147
-           <span class=\"date\">Date</span>
148
-           <p>Content</p>
149
-         </article>
150
-       </main>
151
-     </body>
152
-     </html>"))
130
+  (-to-feed doc type :feed-link feed-link))
131
+
132
+
133
+;(defun -get-items (feed xml-dom &key type)
134
+;  (with-slots (items) feed
135
+;    (loop for item across (get-items xml-dom type)
136
+;          do (push (make-item xml-dom type) items)
137
+;          finally (return items)))) 
153 138
 
154 139
 (defun make-feed (&key title link items feed-link description)
155
-  (make-instance 'feed :title title :link link :items items :feed-link feed-link :description description))
140
+  (make-instance 'feed
141
+                 :description description
142
+                 :feed-link feed-link
143
+                 :items items
144
+                 :link link
145
+                 :title title))
156 146
 
157 147
 (let ((n 0))
158 148
   (defun next-id ()
... ...
@@ -169,10 +159,6 @@
169 159
   (let ((link (cl-ppcre:regex-replace "^https?:" link "")))
170 160
     (subseq link 0 (min 30 (length link)))))
171 161
 
172
-(defun rdf-to-feed (xml-dom))
173
-(defun json-to-feed (json-object))
174
-(defun html5-to-feed (html-dom))
175
-
176 162
 (defmethod push-item ((feed feed) (item item))
177 163
   (push item (slot-value feed 'items)))
178 164
 
... ...
@@ -33,7 +33,7 @@
33 33
 (defun make-person (name &optional uri email)
34 34
   (make-instance 'atom-person :name name :uri uri :email email))
35 35
 
36
-(defmethod alimenta::%get-items (xml-dom (feed-type (eql :atom)))
36
+(defmethod alimenta::get-items (xml-dom (feed-type (eql :atom)))
37 37
   ($ (inline xml-dom) "feed > entry"))
38 38
 
39 39
 (defun get-link (xml)
... ...
@@ -64,7 +64,7 @@
64 64
          (content (with-output-to-string (s)
65 65
                     (awhen (or item-content item-description) (serialize  (parse it) s)))))
66 66
     (make-instance 'atom-item
67
-		   :doc xml-dom
67
+         :doc xml-dom
68 68
                    :content content
69 69
                    :date (local-time:parse-timestring item-date)
70 70
                    :id item-guid
... ...
@@ -81,9 +81,8 @@
81 81
                     ($ (inline author) "> uri" (text) (node))
82 82
                     ($ (inline author) "> email" (text) (node))))))
83 83
 
84
-(defmethod %to-feed (xml-dom (type (eql :atom)) &key feed-link)
85
-  (declare (ignore type) (ignorable feed-link))
86
-  ; TODO: store feed-link
84
+(defmethod alimenta::-to-feed (xml-dom (type (eql :atom)) &key feed-link)
85
+  (declare (ignore type))
87 86
   (flet ((get-feed-elem (selector) ($ (inline xml-dom) selector (text) (node)))
88 87
          (get-feed-elem-attr (selector attr) ($ (inline xml-dom) selector (attr attr) (node))))
89 88
     (let ((doc-title (get-feed-elem "feed > title"))
... ...
@@ -133,7 +132,7 @@
133 132
                       (continue c))))
134 133
      (defconstants ,@constants)))
135 134
 
136
-(defmethod %generate-xml ((feed feed) (feed-type (eql :atom)) &key partial)
135
+(defmethod generate-xml ((feed feed) (feed-type (eql :atom)) &key partial)
137 136
   (let ((feed-root (or ($1 (inline partial) "feed")
138 137
                     (plump:make-element (plump:make-root) "feed"))))
139 138
     (prog1 feed-root
... ...
@@ -152,7 +151,7 @@
152 151
              )))))
153 152
 
154 153
 
155
-(defmethod %generate-xml ((item item) (feed-type (eql :atom)) &key partial)
154
+(defmethod generate-xml ((item item) (feed-type (eql :atom)) &key partial)
156 155
   (let ((parent (if (string-equal (tag-name partial) "feed")
157 156
                   partial
158 157
                   (plump:make-element (plump:make-root) "feed"))))
... ...
@@ -244,7 +243,7 @@
244 243
 
245 244
 (deftest to-feed ()
246 245
   (let ((xml (parse +feed1+)))
247
-    (symbol-macrolet ((feed (alimenta::%to-feed xml :atom)))
246
+    (symbol-macrolet ((feed (alimenta::-to-feed xml :atom)))
248 247
       (should be equal +feed-title+ (slot-value feed 'alimenta:title))
249 248
       (should be equal +feed-link-website+ (slot-value feed 'alimenta:link))
250 249
       (should be equal +feed-link-self+ (slot-value feed 'alimenta:feed-link))
... ...
@@ -256,7 +255,7 @@
256 255
 
257 256
       (should be equal +feed-category-term+
258 257
               (slot-value
259
-                (elt 
258
+                (elt
260 259
                   (slot-value feed 'categories)
261 260
                   0)
262 261
                 'term))
... ...
@@ -297,7 +296,7 @@
297 296
 (deftest generate-xml ()
298 297
   (let* ((xml ($ (inline (parse +entry1+)) "entry" (node)))
299 298
          (item (alimenta::make-item xml :atom)))
300
-    (symbol-macrolet ((generated-xml (alimenta::%generate-xml item :atom)))
299
+    (symbol-macrolet ((generated-xml (alimenta::generate-xml item :atom)))
301 300
       (should be equal +title+
302 301
               ($ (inline generated-xml) "entry > title" (text) (node)))
303 302
       (should be equal +author+
... ...
@@ -4,6 +4,9 @@
4 4
   (:use #:cl #:should-test #:lquery #:plump #:alexandria #:anaphora)
5 5
   (:export #:define-data-class))
6 6
 
7
+(defpackage #:alimenta2
8
+  (:use #:cl #:alexandria #:serapeam #:fw.lu #:should-test))
9
+
7 10
 (defpackage #:alimenta
8 11
   (:use #:cl #:should-test #:lquery #:plump #:alexandria #:anaphora)
9 12
   (:export #:to-feed #:generate-xml #:feed #:title #:link #:items #:feed-link
... ...
@@ -49,6 +49,7 @@
49 49
             (slot-value self 'domain))))
50 50
 
51 51
 (defun get-date (str)
52
+  (declare (optimize (debug 3)))
52 53
   (handler-case
53 54
     (local-time:parse-timestring str)
54 55
     (local-time::invalid-timestring (c) (declare (ignore c))
... ...
@@ -59,10 +60,17 @@
59 60
                  (minute-offset (if (and res (> (length (elt groups 1)) 3))
60 61
                                   (* (signum hour-offset) (parse-integer (elt groups 1) :start 3))
61 62
                                   0)))
62
-            (let-each (:be *)
63
-              (chronicity:parse timestamp)
64
-              (local-time:timestamp- * minute-offset :minute)
65
-              (local-time:timestamp- * hour-offset   :hour))))))))
63
+            (loop
64
+              (restart-case (return
65
+                              (let-each (:be *)
66
+                                (chronicity:parse timestamp)
67
+                                (local-time:timestamp- * minute-offset :minute)
68
+                                (local-time:timestamp- * hour-offset   :hour)))       
69
+                (pop-token () (setf timestamp
70
+                                    (subseq timestamp
71
+                                            0
72
+                                            (position #\space timestamp
73
+                                                      :from-end t))))))))))))
66 74
 
67 75
 
68 76
 (defmethod primary-value ((self rss-image))
... ...
@@ -89,10 +97,10 @@
89 97
      (combine (text) (attr "domain"))
90 98
      (map-apply #'make-category)))
91 99
 
92
-(defmethod %get-items (xml-dom (feed-type (eql :rss)))
100
+(defmethod get-items (xml-dom (feed-type (eql :rss)))
93 101
   ($ (inline xml-dom) "channel > item"))
94 102
 
95
-(defmethod %generate-xml ((item item) (feed-type (eql :rss)) &key partial)
103
+(defmethod generate-xml ((item item) (feed-type (eql :rss)) &key partial)
96 104
   (prog1 partial
97 105
     (let ((item-root (make-element ($1 (inline partial) "channel") "item")))
98 106
       (flet ((make-element (tag) (make-element item-root tag)))
... ...
@@ -106,7 +114,7 @@
106 114
             "isPermaLink"
107 115
             "false"))))))
108 116
 
109
-(defmethod %generate-xml ((feed feed) (feed-type (eql :rss)) &rest r)
117
+(defmethod generate-xml ((feed feed) (feed-type (eql :rss)) &rest r)
110 118
   (declare (ignore r))
111 119
   (let* ((xml-root (plump:make-root))
112 120
          (feed-root (plump:make-element xml-root "rss"))
... ...
@@ -163,7 +171,7 @@
163 171
           (local-time:parse-timestring "2016-01-09T23:00:00.000000-0100")
164 172
           (get-date "Fri, 09 Jan 2016 21:30:00 -0230"))) 
165 173
 
166
-(defmethod %to-feed (xml-dom (type (eql :rss)) &key feed-link)
174
+(defmethod alimenta::-to-feed (xml-dom (type (eql :rss)) &key feed-link)
167 175
   ; TODO: store feed-link
168 176
   (flet ((get-channel-element (el)
169 177
            ($ (inline xml-dom) el (text) (node))))