git.fiddlerwoaroof.com
Browse code

Various updates: reworking the classes, etc.

fiddlerwoaroof authored on 07/03/2016 04:10:47
Showing 7 changed files
... ...
@@ -11,10 +11,13 @@
11 11
                #:anaphora
12 12
                #:chronicity
13 13
                #:fwoar.lisputils
14
+               #:split-sequence
14 15
                #:drakma)
15 16
   :serial t
16 17
   :components ((:file "package")
17 18
                (:file "alimenta")  
19
+               (:file "data-class")
20
+               (:file "date-handling")
18 21
                (:file "atom")  
19 22
                (:file "rss")  
20 23
                (:file "fetching")
... ...
@@ -6,7 +6,7 @@
6 6
 (defclass feed () 
7 7
   ((title :initarg :title :initform nil)
8 8
    (link :initarg :link :initform nil)
9
-   (items :initarg :items :initform nil)
9
+   (items :initarg :items :initform nil :accessor items)
10 10
    (description :initarg :description :initform nil)
11 11
    (feed-link :initarg :feed-link :initform nil)
12 12
    (doc :initarg :doc :initform nil)
... ...
@@ -19,7 +19,7 @@
19 19
    (date :initarg :date :initform nil)
20 20
    (link :initarg :link :initform nil)
21 21
    (links :initform (make-hash-table :test #'equalp))
22
-   (content :initarg :content :initform nil)
22
+   (content :initarg :content :initform nil :accessor content)
23 23
    (doc :initarg :doc :initform nil)))
24 24
 
25 25
 (defclass complex-value () ())
... ...
@@ -6,17 +6,11 @@
6 6
    (label :initarg :label :initform nil)
7 7
    (scheme :initarg :scheme :initform nil)))
8 8
 
9
-(defun make-category (term &optional label scheme)
10
-  (make-instance 'atom-category :term term :label label :scheme scheme))
11
-
12 9
 (defclass atom-person ()
13 10
   ((name  :initarg :name  :type (or null string) :initform nil)
14 11
    (uri   :initarg :uri   :type (or null string) :initform nil)
15 12
    (email :initarg :email :type (or null string) :initform nil)))
16 13
 
17
-(defun make-person (name &optional uri email)
18
-  (make-instance 'atom-person :name name :uri uri :email email))
19
-
20 14
 (defclass atom-feed (alimenta:feed)
21 15
   ((subtitle   :initarg :subtitle                        :initform nil)
22 16
    (id         :initarg :id                              :initform nil)
... ...
@@ -26,16 +20,22 @@
26 20
    (updated    :initarg :updated                         :initform nil)
27 21
    (authors    :initarg :authors    :type (or null list) :initform nil)))
28 22
 
23
+(defclass alimenta::link ()
24
+  ((alimenta::relation :initarg :rel)
25
+   (alimenta::target   :initarg :target)))
26
+
29 27
 (defclass atom-item (alimenta:item)
30 28
   ((author-uri :initarg :author-uri :initform nil)))
31 29
 
30
+(defun make-category (term &optional label scheme)
31
+  (make-instance 'atom-category :term term :label label :scheme scheme))
32
+
33
+(defun make-person (name &optional uri email)
34
+  (make-instance 'atom-person :name name :uri uri :email email))
35
+
32 36
 (defmethod alimenta::%get-items (xml-dom (feed-type (eql :atom)))
33 37
   ($ (inline xml-dom) "feed > entry"))
34 38
 
35
-(defclass alimenta::link ()
36
-  ((alimenta::relation :initarg :rel)
37
-   (alimenta::target   :initarg :target)))
38
-
39 39
 (defun get-link (xml)
40 40
   "This only handles alternate links"
41 41
   (let ((links ($ (inline xml) "> link[rel=alternate]" (combine (attr :type) (attr :href)))))
... ...
@@ -28,20 +28,56 @@
28 28
               `(setf ,name (when it (,transform it)))
29 29
               `(setf ,name it)))))))
30 30
 
31
-(defgeneric all-slots (self format))
31
+(defgeneric %all-slots (self format))
32
+(defun all-slots (self &optional format)
33
+  (%all-slots self format))
34
+
35
+(defgeneric slot-tags (self))
36
+
37
+(defun process-slots-for-data-class (slots)
38
+  (mapcar
39
+    (fw.lu::destructuring-lambda ((slot tag . rest))
40
+      (let ((tag (etypecase tag
41
+                   (symbol (string-downcase tag))
42
+                   (string tag))))
43
+        (list* slot (make-keyword slot) tag rest)))
44
+    (fw.lu:ensure-mapping slots)))
45
+
46
+(deftest process-slots-for-data-class ()
47
+  (let ((tc-1 '(a))
48
+        (tc-2 '((a b)))
49
+        (tc-3 '((a "b")))
50
+        (tc-4 '((a b c)))
51
+        (tc-5 '((a "b" c)))
52
+        (tc-6 '((a "bC")))
53
+        (tc-7 '((a "bC" d))))
54
+    (should be equal '((a :a "a")) (process-slots-for-data-class tc-1))
55
+    (should be equal '((a :a "b")) (process-slots-for-data-class tc-2))
56
+    (should be equal '((a :a "b")) (process-slots-for-data-class tc-3))
57
+    (should be equal '((a :a "b" c)) (process-slots-for-data-class tc-4))
58
+    (should be equal '((a :a "b" c)) (process-slots-for-data-class tc-5))
59
+    (should be equal '((a :a "bC")) (process-slots-for-data-class tc-6))
60
+    (should be equal '((a :a "bC" d)) (process-slots-for-data-class tc-7))))
32 61
 
33 62
 (defmacro define-data-class (name (doc-slot root-el) (&rest superclasses) &body slots)
34
-  `(progn
35
-     (defclass ,name ,superclasses
36
-       ,(loop for (slot) in (fw.lu:ensure-mapping slots)
37
-              collect `(,slot :initarg ,(make-keyword slot) :accessor ,slot)))
38
-     ,@(loop for (slot tag-name . rest) in (fw.lu:ensure-mapping slots)
39
-             collect `(lazy-load-slot ,name ,doc-slot ,root-el ,slot ,tag-name ,@rest))
40
-     (defmethod all-slots ((self ,name) format)
41
-       (pairlis (list ,@(mapcar (fw.lu::alambda (make-keyword (car it)))
42
-                                (fw.lu:ensure-mapping slots)))
43
-                (list ,@(loop for (slot) in (fw.lu:ensure-mapping slots)
44
-                              collect `(,slot self)))))))
63
+  (declare (optimize (debug 3)))
64
+  (flet ((make-slot-spec (slot slot-keyword)
65
+           `(,slot :initarg ,slot-keyword :accessor ,slot)))
66
+    (let ((slots (process-slots-for-data-class slots)))
67
+      `(progn
68
+         (defclass ,name ,superclasses
69
+           ((slot-tags :allocation :class :initform
70
+                       ',(loop for (_ slot-keyword tag) in slots
71
+                               collect (cons slot-keyword tag)))
72
+            ,@(mapcar (fw.lu::destructuring-lambda ((slot slot-keyword . r))
73
+                        (declare (ignore r))
74
+                        (make-slot-spec slot slot-keyword))
75
+                      slots)))
76
+         ,@(loop for (slot _ tag-name . rest) in slots
77
+                 collect `(lazy-load-slot ,name ,doc-slot ,root-el ,slot ,tag-name ,@rest))
78
+         (defmethod %all-slots ((self ,name) format)
79
+           (pairlis (list ,@(mapcar (fw.lu::alambda (cadr it)) slots))
80
+                    (list ,@(loop for (slot) in slots collect `(,slot self)))))))))
45 81
 
46 82
 
47 83
 
... ...
@@ -1,11 +1,16 @@
1 1
 (declaim (optimize (speed 0) (safety 3) (debug 3)))
2 2
 (in-package :alimenta.pull-feed)
3 3
 
4
+(defmacro setup-libraries-for-feeds (&body body)
5
+  `(let ((plump:*tag-dispatchers* plump:*xml-tags*)
6
+         (drakma:*text-content-types* 
7
+           (pairlis '("application" "application")
8
+                    '("atom+xml"    "rss+xml")
9
+                    drakma:*text-content-types*)))
10
+     ,@body))
11
+
4 12
 (defun fetch-doc-from-url (url)
5
-  (let ((plump:*tag-dispatchers* plump:*xml-tags*)
6
-        (drakma:*text-content-types* (concatenate 'list
7
-                                                  '(("application" . "atom+xml") ("application" . "rss+xml"))
8
-                                                  drakma:*text-content-types*)))
13
+  (setup-libraries-for-feeds 
9 14
     (plump:parse (drakma:http-request url))))
10 15
 
11 16
 (define-condition fetch-error () ())
... ...
@@ -20,21 +25,27 @@
20 25
   (cerror "Skip this feed" 'no-feed :url url))
21 26
 
22 27
 (defun fetch-feed-from-url (url &key type)
23
-  (let* ((feeds (alimenta.discover:discover-feed (drakma:http-request url)))
24
-         (feeds (if type (remove-if-not (lambda (x) (eql type (car x))) feeds) feeds)))
25
-    (format t "~a << type" type)
26
-    (if (not feeds) (no-feed url)
27
-      (fetch-doc-from-url
28
-        (cdar 
29
-          (restart-case
30
-            (if (cdr feeds) (feed-ambiguous feeds) feeds)
31
-            (take-first-feed nil
32
-                             :report (lambda (s) (format s "Take the first feed"))
33
-                             feeds)
34
-            (take-nth-feed (n)
35
-                           :report (lambda (s) (format s "Take the nth feed"))
36
-                           (list (elt feeds n)))
37
-            (select-feed (selector)
38
-                         :report (lambda (s) (format s "Provide a function to select the right feed"))
39
-                         (find-if selector feeds))))))))
28
+  (setup-libraries-for-feeds
29
+    (let* ((feeds (alimenta.discover:discover-feed (drakma:http-request url)))
30
+           (feeds (if type (remove-if-not (lambda (x) (eql type (car x))) feeds) feeds)))
31
+      (if (not feeds) (no-feed url)
32
+        (fetch-doc-from-url
33
+          (cdar 
34
+            (restart-case
35
+              (if (cdr feeds) (feed-ambiguous feeds) feeds)
36
+              (take-first-feed nil
37
+                               :report (lambda (s) (format s "Take the first feed"))
38
+                               feeds)
39
+              (take-nth-feed (n)
40
+                             :report (lambda (s) (format s "Take the nth feed"))
41
+                             (list (elt feeds n)))
42
+              (select-feed (selector)
43
+                           :report (lambda (s) (format s "Provide a function to select the right feed"))
44
+                           (find-if selector feeds)))))))))
40 45
 
46
+(defun pull-feed (url &key detect type)
47
+  (to-feed
48
+    (if detect
49
+      (fetch-feed-from-url url)
50
+      (fetch-doc-from-url url)) 
51
+    :type type))
... ...
@@ -6,16 +6,18 @@
6 6
 
7 7
 (defpackage #:alimenta
8 8
   (:use #:cl #:should-test #:lquery #:plump #:alexandria #:anaphora)
9
-  (:export #:to-feed #:generate-xml
10
-           #:feed #:title #:link #:items #:feed-link #:doc #:source-type #:id #:date #:content
11
-           #:item #:description #:%generate-xml #:%to-feed #:%get-items #:make-item #:complex-value
9
+  (:export #:to-feed #:generate-xml #:feed #:title #:link #:items #:feed-link
10
+           #:doc #:source-type #:id #:date #:content #:item #:description
11
+           #:%generate-xml #:%to-feed #:%get-items #:make-item #:complex-value
12 12
            #:primary-value))
13 13
 
14 14
 (defpackage #:alimenta.rss
15
-  (:use #:cl #:should-test #:lquery #:plump #:alexandria #:anaphora #:alimenta)
16
-  (:export #:language #:copyright #:managing-editor #:webmaster #:publication-date #:last-build-date
17
-           #:categories #:generator #:docs #:cloud #:ttl #:image #:rating #:text-input #:skip-hours
18
-           #:skip-days #:rss-feed #:rss-item))
15
+  (:use #:cl #:should-test #:lquery #:plump #:alexandria #:anaphora #:alimenta #:data-class
16
+        #:fwoar.lisputils)
17
+  (:export #:language #:copyright #:managing-editor #:webmaster
18
+           #:publication-date #:last-build-date #:categories #:generator #:docs
19
+           #:cloud #:ttl #:image #:rating #:text-input #:skip-hours #:skip-days
20
+           #:rss-feed #:rss-item))
19 21
 
20 22
 (defpackage #:alimenta.atom
21 23
   (:use #:cl #:should-test #:lquery #:plump #:alexandria #:anaphora #:alimenta))
... ...
@@ -26,7 +28,8 @@
26 28
 
27 29
 (defpackage #:alimenta.pull-feed 
28 30
   (:use #:cl #:alimenta #:alexandria #:anaphora #:lquery)
29
-  (:export #:pull-feed #:fetch-doc-from-url)) 
31
+  (:export #:pull-feed #:fetch-doc-from-url #:fetch-feed-from-url
32
+           #:fetch-error #:feed-ambiguous #:no-feed)) 
30 33
 
31 34
 (defmethod asdf:perform ((o asdf:test-op) (s (eql (asdf:find-system :alimenta))))
32 35
   (asdf:load-system :alimenta)
... ...
@@ -1,20 +1,5 @@
1 1
 (declaim (optimize (speed 0) (safety 3) (debug 3)))
2
-
3 2
 (in-package :alimenta.rss)
4
-(defun get-date (str)
5
-  (handler-case
6
-    (local-time:parse-timestring str)
7
-    (local-time::invalid-timestring (c) (declare (ignore c))
8
-      (multiple-value-bind (res groups) (cl-ppcre:scan-to-strings "(.*)\s*([+-][0-9]{2,4})\s?$" str)
9
-        (let ((local-time:*default-timezone* local-time:+utc-zone+))
10
-          (let* ((timestamp (string-trim " " (if res (elt groups 0) str)))
11
-                 (hour-offset (if res (parse-integer (elt groups 1) :end 3) 0)) 
12
-                 (minute-offset (if (and res (> (length (elt groups 1)) 3))
13
-                                  (* (signum hour-offset) (parse-integer (elt groups 1) :start 3))
14
-                                  0)))
15
-                  
16
-            (local-time:timestamp- (local-time:timestamp- (chronicity:parse timestamp) minute-offset :minute)
17
-                                   hour-offset :hour)))))))
18 3
 
19 4
 
20 5
 (defclass rss-image ()
... ...
@@ -25,44 +10,10 @@
25 10
    (height :initarg :height :initform nil)
26 11
    (description :initarg :description :initform nil)))
27 12
 
28
-(defmethod print-object ((self rss-image) stream)
29
-  (print-unreadable-object (self stream :type t :identity t)
30
-    (format stream "~a" (slot-value self 'url))))
31
-
32
-(defmethod primary-value ((self rss-image))
33
-  (slot-value self 'url))
34
-
35
-(defun make-image (url title &optional link width height description)
36
-  (let ((link (or link url)))
37
-    (make-instance 'rss-image
38
-                   :url url
39
-                   :title title
40
-                   :link link
41
-                   :width width
42
-                   :height height
43
-                   :description description)))
44
-
45 13
 (defclass rss-category ()
46 14
   ((category :initarg :category :initform nil)
47 15
    (domain :initarg :domain :initform nil)))
48 16
 
49
-(defmethod print-object ((self rss-category) stream)
50
-  (print-unreadable-object (self stream :type t :identity t)
51
-    (format stream "~a~@[ ~a~]"
52
-            (slot-value self 'category)
53
-            (slot-value self 'domain))))
54
-
55
-(defmethod primary-value ((self rss-category))
56
-  (slot-value self 'category))
57
-
58
-(defun make-category (category &optional domain)
59
- (make-instance 'rss-category :category category :domain domain))
60
-
61
-(defun get-categories (doc tag)
62
-  ($ (inline doc) tag
63
-     (combine (text) (attr "domain"))
64
-     (map-apply #'make-category)))
65
-
66 17
 (define-data-class rss-feed (doc "channel") (feed)
67 18
   language copyright webmaster
68 19
   generator docs cloud ttl rating
... ...
@@ -87,6 +38,57 @@
87 38
   (categories "category" :value (get-categories doc "> category"))
88 39
   source comments enclosure )
89 40
 
41
+(defmethod print-object ((self rss-image) stream)
42
+  (print-unreadable-object (self stream :type t :identity t)
43
+    (format stream "~a" (slot-value self 'url))))
44
+
45
+(defmethod print-object ((self rss-category) stream)
46
+  (print-unreadable-object (self stream :type t :identity t)
47
+    (format stream "~a~@[ ~a~]"
48
+            (slot-value self 'category)
49
+            (slot-value self 'domain))))
50
+
51
+(defun get-date (str)
52
+  (handler-case
53
+    (local-time:parse-timestring str)
54
+    (local-time::invalid-timestring (c) (declare (ignore c))
55
+      (multiple-value-bind (res groups) (cl-ppcre:scan-to-strings "(.*)\s*([+-][0-9]{2,4})\s?$" str)
56
+        (let ((local-time:*default-timezone* local-time:+utc-zone+))
57
+          (let* ((timestamp (string-trim " " (if res (elt groups 0) str)))
58
+                 (hour-offset (if res (parse-integer (elt groups 1) :end 3) 0)) 
59
+                 (minute-offset (if (and res (> (length (elt groups 1)) 3))
60
+                                  (* (signum hour-offset) (parse-integer (elt groups 1) :start 3))
61
+                                  0)))
62
+            (let-each (:be *)
63
+              (chronicity:parse timestamp)
64
+              (local-time:timestamp- * minute-offset :minute)
65
+              (local-time:timestamp- * hour-offset   :hour))))))))
66
+
67
+
68
+(defmethod primary-value ((self rss-image))
69
+  (slot-value self 'url))
70
+
71
+(defun make-image (url title &optional link width height description)
72
+  (let ((link (or link url)))
73
+    (make-instance 'rss-image
74
+                   :url url
75
+                   :title title
76
+                   :link link
77
+                   :width width
78
+                   :height height
79
+                   :description description)))
80
+
81
+(defmethod primary-value ((self rss-category))
82
+  (slot-value self 'category))
83
+
84
+(defun make-category (category &optional domain)
85
+ (make-instance 'rss-category :category category :domain domain))
86
+
87
+(defun get-categories (doc tag)
88
+  ($ (inline doc) tag
89
+     (combine (text) (attr "domain"))
90
+     (map-apply #'make-category)))
91
+
90 92
 (defmethod %get-items (xml-dom (feed-type (eql :rss)))
91 93
   ($ (inline xml-dom) "channel > item"))
92 94
 
... ...
@@ -102,10 +104,7 @@
102 104
           (plump-dom:set-attribute
103 105
             ($ (inline (make-element "guid")) (text id) (node))
104 106
             "isPermaLink"
105
-            "false") 
106
-          ))
107
-
108
-      )))
107
+            "false"))))))
109 108
 
110 109
 (defmethod %generate-xml ((feed feed) (feed-type (eql :rss)) &rest r)
111 110
   (declare (ignore r))