Browse code
Add sbcl support
fiddlerwoaroof authored on 15/02/2017 09:45:15
Showing 6 changed files
Showing 6 changed files
... | ... |
@@ -1,4 +1,8 @@ |
1 | 1 |
(load (truename "~/quicklisp/setup.lisp")) |
2 | 2 |
(push "/home/edwlan/github_repos/alimenta-feed-archive/" asdf:*central-registry*) |
3 | 3 |
(ql:quickload :alimenta-feed-archive) |
4 |
-(save-lisp-and-die "feed-archiver" :executable t :toplevel #'alimenta.feed-archive::command-line-main) |
|
4 |
+(#+ccl ccl:save-application |
|
5 |
+ #+sbcl save-lisp-and-die |
|
6 |
+ "feed-archiver" |
|
7 |
+ #+sbcl :executable #+ccl :prepend-kernel t |
|
8 |
+ #+sbcl :toplevel #+ccl :toplevel-function #'alimenta.feed-archive::command-line-main) |
... | ... |
@@ -9,19 +9,19 @@ |
9 | 9 |
;; Work around some issue with pathnames |
10 | 10 |
(setf path (merge-pathnames path (make-pathname :type :unspecific)))) |
11 | 11 |
|
12 |
-(defun %encode-item (root-dir item) |
|
13 |
- (destructuring-bind (title path) item |
|
14 |
- (format t "~&Encoding ~a~%" title) |
|
15 |
- (restart-once (fix-pathname () (fix-path path)) |
|
16 |
- (let ((pathname (uiop:enough-pathname path root-dir))) |
|
17 |
- (yason:with-object () |
|
18 |
- (yason:encode-object-element "title" title) |
|
19 |
- (yason:encode-object-element "path" pathname)))))) |
|
20 |
- |
|
21 | 12 |
(defun skip-item () |
22 | 13 |
(when-let ((restart (find-restart 'skip-item))) |
23 | 14 |
(invoke-restart restart))) |
24 | 15 |
|
16 |
+(define-condition feed-error (error) |
|
17 |
+ ((%feed :reader the-feed :initarg :feed :initform (error "We need a feed")) |
|
18 |
+ (%condition :reader the-condition :initarg :condition :initform (error "feed-error must wrap a condition")))) |
|
19 |
+ |
|
20 |
+(defun wrap-condition (condition feed) |
|
21 |
+ (error 'feed-error |
|
22 |
+ :feed feed |
|
23 |
+ :condition condition)) |
|
24 |
+ |
|
25 | 25 |
(defun %encode-feed-as-json (feed item-storage-info root-dir &optional stream) |
26 | 26 |
(with-accessors ((description alimenta:description) |
27 | 27 |
(feed-link alimenta:feed-link) |
... | ... |
@@ -35,22 +35,32 @@ |
35 | 35 |
(yason:with-object-element ("items") |
36 | 36 |
(yason:with-array () |
37 | 37 |
(dolist (item item-storage-info) |
38 |
+ |
|
38 | 39 |
(with-simple-restart (skip-item "Skip item ~s" (car item)) |
39 |
- (%encode-item root-dir item)))))))) |
|
40 |
+ ;; (format t "~&I Store Info: ~a~%~4t~a~%" (uiop:unix-namestring (cadr item)) root-dir) |
|
41 |
+ (%encode-item root-dir item) |
|
42 |
+ #+null |
|
43 |
+ (yason:encode-array-element (uiop:unix-namestring (uiop:enough-pathname root-dir (cadr item)))) |
|
44 |
+ ))))))) |
|
40 | 45 |
|
41 |
-(defmethod store ((item alimenta:item) (directory pathname)) |
|
42 |
- (flet ((get-item-store-name (item directory) |
|
43 |
- (let ((id (get-id item))) |
|
44 |
- (merge-pathnames (make-pathname :name id) directory)))) |
|
45 |
- |
|
46 |
- (prog1-let ((item-title (alimenta:title item)) |
|
47 |
- (fn (get-item-store-name item directory))) |
|
48 |
- (with-open-file (item-f fn :direction :output) |
|
49 |
- (yason:encode item item-f))))) |
|
46 |
+(defmethod store ((items sequence) (directory pathname)) |
|
47 |
+ (map 'list (lambda (item) (store item directory)) |
|
48 |
+ (stable-sort |
|
49 |
+ (sort (remove-if #'older-than-a-week items :key #'alimenta:date) |
|
50 |
+ #'string-lessp |
|
51 |
+ :key #'alimenta:title) |
|
52 |
+ #'local-time:timestamp> |
|
53 |
+ :key #'alimenta:date))) |
|
50 | 54 |
|
51 |
-(define-condition feed-error (error) |
|
52 |
- ((%feed :initarg :feed :initform (error "We need a feed")) |
|
53 |
- (%condition :initarg :condition :initform (error "feed-error must wrap a condition")))) |
|
55 |
+(defun sort-and-filter-items (feed) |
|
56 |
+ (setf (alimenta:items feed) |
|
57 |
+ (stable-sort |
|
58 |
+ (sort (remove-if #'older-than-a-week (alimenta:items feed) |
|
59 |
+ :key #'alimenta:date) |
|
60 |
+ #'string-lessp |
|
61 |
+ :key #'alimenta:title) |
|
62 |
+ #'local-time:timestamp> |
|
63 |
+ :key #'alimenta:date))) |
|
54 | 64 |
|
55 | 65 |
(defmethod store ((feed alimenta:feed) (directory pathname)) |
56 | 66 |
(flet ((get-feed-store-name (feed directory) |
... | ... |
@@ -63,6 +73,7 @@ |
63 | 73 |
(link alimenta:link) |
64 | 74 |
(source-type alimenta:source-type) |
65 | 75 |
(title alimenta:title)) feed |
76 |
+ ; We wrap all errors with our own condition |
|
66 | 77 |
(handler-bind ((error (lambda (c) (error 'feed-error :feed feed :condition c)))) |
67 | 78 |
(prog1-let ((feed-title title) |
68 | 79 |
(feed-store (get-feed-store-name feed directory))) |
... | ... |
@@ -73,12 +84,40 @@ |
73 | 84 |
feed-store |
74 | 85 |
index))))))) |
75 | 86 |
|
76 |
-(defmethod store ((items sequence) (directory pathname)) |
|
77 |
- (map 'list (lambda (item) (store item directory)) |
|
78 |
- (stable-sort |
|
79 |
- (sort (remove-if #'older-than-a-week items :key #'alimenta:date) |
|
80 |
- #'string-lessp |
|
81 |
- :key #'alimenta:title) |
|
82 |
- #'local-time:timestamp> |
|
83 |
- :key #'alimenta:date))) |
|
87 |
+(defmethod store ((feed alimenta:feed) (stream stream)) |
|
88 |
+ (handler-bind ((error (lambda (c) |
|
89 |
+ (typecase c |
|
90 |
+ (feed-error c) |
|
91 |
+ (t (wrap-condition c feed)))))) |
|
92 |
+ (yason:with-output (stream :indent t) |
|
93 |
+ (yason:with-object () |
|
94 |
+ (yason:with-object-element ("metadata") |
|
95 |
+ (yason:encode-object feed)) |
|
96 |
+ (yason:with-object-element ("items") |
|
97 |
+ (yason:with-array () |
|
98 |
+ (for:for ((item over feed)) |
|
99 |
+ (store item stream)))))))) |
|
100 |
+ |
|
101 |
+(defun %encode-item (root-dir item) |
|
102 |
+ (destructuring-bind (title path) item |
|
103 |
+ (format t "~&Encoding ~a~%" title) |
|
104 |
+ (restart-once (fix-pathname () (fix-path path)) |
|
105 |
+ (let ((pathname (uiop:enough-pathname path root-dir))) |
|
106 |
+ (yason:with-object () |
|
107 |
+ (yason:encode-object-element "title" title) |
|
108 |
+ (yason:encode-object-element "path" pathname)))))) |
|
109 |
+ |
|
110 |
+(defmethod store ((item alimenta:item) (directory pathname)) |
|
111 |
+ (flet ((get-item-store-name (item directory) |
|
112 |
+ (let ((id (get-id item))) |
|
113 |
+ (merge-pathnames (make-pathname :name id :version nil :type "json") directory)))) |
|
114 |
+ |
|
115 |
+ (prog1-let ((item-title (alimenta:title item)) |
|
116 |
+ (fn (get-item-store-name item directory))) |
|
117 |
+ (with-open-file (item-f fn :direction :output) |
|
118 |
+ (yason:encode item item-f))))) |
|
119 |
+ |
|
120 |
+(defmethod store ((item alimenta:item) (stream stream)) |
|
121 |
+ (yason:with-output (stream :indent t) |
|
122 |
+ (yason:encode-object item))) |
|
84 | 123 |
|
... | ... |
@@ -21,8 +21,8 @@ |
21 | 21 |
|
22 | 22 |
(defun test-feed-list () |
23 | 23 |
(values '("http://feeds.feedburner.com/GamasutraFeatureArticles/" |
24 |
- "http://feeds.feedburner.com/GamasutraNews/" |
|
25 |
- "http://feeds.feedburner.com/GamasutraColumns/") |
|
24 |
+ "https://www.codinghorror.com/blog/index.xml" |
|
25 |
+ "https://sancrucensis.wordpress.com/feed/") |
|
26 | 26 |
#p"/tmp/feed-archive/")) |
27 | 27 |
|
28 | 28 |
(defun init-feeds (&key feed-list archive-root) |
... | ... |
@@ -40,8 +40,9 @@ |
40 | 40 |
:test #'equalp)) |
41 | 41 |
|
42 | 42 |
(defun safe-pull-feed (feed-url) |
43 |
- "Handles date parsing errors in the feed: chronicity won't parse certain date formats, this catches the error |
|
44 |
-and modifies the format to something chronicity can handle." |
|
43 |
+ "Handles date parsing errors in the feed: chronicity won't parse |
|
44 |
+ certain date formats, this catches the error and modifies the |
|
45 |
+ format to something chronicity can handle." |
|
45 | 46 |
(let ((pop-times 0)) |
46 | 47 |
(flet ((pop-50-tokens (c) |
47 | 48 |
(declare (ignore c)) |
... | ... |
@@ -52,24 +53,31 @@ and modifies the format to something chronicity can handle." |
52 | 53 |
pop-times) |
53 | 54 |
(alimenta.rss::pop-token)) |
54 | 55 |
(continue))))) |
55 |
- (handler-bind ((error #'pop-50-tokens)) |
|
56 |
+ (handler-bind ((warning #'muffle-warning) |
|
57 |
+ (error #'pop-50-tokens)) |
|
58 |
+ (format t "~&Tring to pull: ~a... " feed-url) |
|
56 | 59 |
(prog1 (alimenta.pull-feed:pull-feed feed-url) |
57 | 60 |
;; Why am I decf-ing here? |
61 |
+ (format t "... Success~%" feed-url) |
|
58 | 62 |
(decf pop-times)))))) |
59 | 63 |
|
60 | 64 |
(defun skip-feed () |
61 | 65 |
(when-let ((restart (find-restart 'skip-feed))) |
62 | 66 |
(invoke-restart restart))) |
63 | 67 |
|
68 |
+(defun pull-and-store-feeds (feeds pull-directory) |
|
69 |
+ (loop for feed-url in feeds |
|
70 |
+ collect |
|
71 |
+ (with-simple-restart (skip-feed "Skip ~a" feed-url) |
|
72 |
+ (let ((feed (safe-pull-feed feed-url))) |
|
73 |
+ (setf (alimenta:feed-link feed) |
|
74 |
+ feed-url) |
|
75 |
+ (store feed pull-directory))))) |
|
76 |
+ |
|
64 | 77 |
(defun archive-feeds () |
65 | 78 |
(let ((pull-time (local-time:now))) |
66 | 79 |
(let* ((pull-directory (get-store-directory-name pull-time)) |
67 |
- (paths (loop for feed-url in *feeds* collect |
|
68 |
- (with-simple-restart (skip-feed "Skip ~a" feed-url) |
|
69 |
- (let ((feed (safe-pull-feed feed-url))) |
|
70 |
- (setf (alimenta:feed-link feed) |
|
71 |
- feed-url) |
|
72 |
- (store feed pull-directory)))))) |
|
80 |
+ (paths (pull-and-store-feeds *feeds* pull-directory))) |
|
73 | 81 |
(with-open-file (index (merge-pathnames "index.json" pull-directory) :direction :output) |
74 | 82 |
(yason:with-output (index :indent t) |
75 | 83 |
(yason:with-object () |
... | ... |
@@ -90,16 +98,33 @@ and modifies the format to something chronicity can handle." |
90 | 98 |
paths))))))))) |
91 | 99 |
|
92 | 100 |
|
101 |
+;; This is an ungodly mess, we need to avoid funneling everything through fix-pathname-or-skip |
|
93 | 102 |
(defun command-line-main (&optional (feed-list-initializer #'init-feeds)) |
94 |
- (flet ((fix-pathname-or-continue (c) |
|
95 |
- (declare (ignorable c)) |
|
96 |
- (format t "~&Received condition ~s~%" c) |
|
97 |
- (if (find-restart 'fix-pathname) |
|
98 |
- (progn (fix-pathname) |
|
99 |
- (format t "~&Fixing pathname...~%")) |
|
100 |
- (progn (format t "~&Skipping a feed...~%") |
|
101 |
- (continue))))) |
|
102 |
- (handler-bind ((error (lambda (c) (fix-pathname-or-continue c)))) |
|
103 |
+ (declare (optimize (debug 3))) |
|
104 |
+ (labels ((feed-type-unsupported (c &key (restart 'skip-feed)) |
|
105 |
+ (format t "~&Feed type unsupported: ~a for feed ~a~%" |
|
106 |
+ (alimenta:feed-type c) |
|
107 |
+ (alimenta:feed-link c)) |
|
108 |
+ (funcall restart)) |
|
109 |
+ (fix-pathname-or-skip (c &key (restart 'skip-feed) (wrapped-condition nil wc-p)) |
|
110 |
+ (let ((wrapped-condition (or wrapped-condition c))) |
|
111 |
+ (typecase wrapped-condition |
|
112 |
+ (alimenta:feed-type-unsupported (feed-type-unsupported)) |
|
113 |
+ (otherwise |
|
114 |
+ (if (find-restart 'fix-pathname) |
|
115 |
+ (progn (fix-pathname) |
|
116 |
+ (format t "~&Fixing pathname...~%")) |
|
117 |
+ (progn (unless (eq restart 'continue) |
|
118 |
+ (format t "~&Skipping a feed... ~s~%" |
|
119 |
+ (if wc-p |
|
120 |
+ (alimenta.feed-archive.encoders::the-feed c) |
|
121 |
+ "Unknown"))) |
|
122 |
+ (funcall restart)))))))) |
|
123 |
+ |
|
124 |
+ (handler-bind ((alimenta.feed-archive.encoders::feed-error |
|
125 |
+ (lambda (c) (fix-pathname-or-skip c :wrapped-condition (alimenta.feed-archive.encoders::the-condition c)))) |
|
126 |
+ (alimenta:feed-type-unsupported #'feed-type-unsupported) |
|
127 |
+ (error (lambda (c) (fix-pathname-or-skip c :restart 'continue)))) |
|
103 | 128 |
(multiple-value-bind (*feeds* *feed-base*) (funcall feed-list-initializer) |
104 | 129 |
(alimenta.pull-feed::with-user-agent ("Feed Archiver v0.1b") |
105 | 130 |
(archive-feeds)))))) |
... | ... |
@@ -28,7 +28,7 @@ |
28 | 28 |
(yason:with-output (stream :indent t) |
29 | 29 |
(yason:encode-object feed))) |
30 | 30 |
|
31 |
-(defmethod yason:encode ((item alimenta:item) &optional stream) |
|
31 |
+(defmethod yason:encode-slots progn ((item alimenta:item)) |
|
32 | 32 |
(with-accessors ((author alimenta::author) |
33 | 33 |
(content alimenta:content) |
34 | 34 |
(date alimenta:date) |
... | ... |
@@ -36,13 +36,16 @@ |
36 | 36 |
(link alimenta:link) |
37 | 37 |
(title alimenta:title)) item |
38 | 38 |
(let* ((date (local-time:format-timestring nil date))) |
39 |
- (yason:with-output (stream :indent t) |
|
40 |
- (yason:with-object () |
|
41 |
- (yason:encode-object-element "title" title) |
|
42 |
- (yason:encode-object-element "date" date) |
|
43 |
- (yason:encode-object-element "author" title) |
|
44 |
- (yason:encode-object-element "id" (princ-to-string id)) |
|
45 |
- (yason:encode-object-element "link" link) |
|
46 |
- (yason:encode-object-element "content" content))))) |
|
39 |
+ (yason:with-object () |
|
40 |
+ (yason:encode-object-element "title" title) |
|
41 |
+ (yason:encode-object-element "date" date) |
|
42 |
+ (yason:encode-object-element "author" title) |
|
43 |
+ (yason:encode-object-element "id" (princ-to-string id)) |
|
44 |
+ (yason:encode-object-element "link" link) |
|
45 |
+ (yason:encode-object-element "content" content))))) |
|
46 |
+ |
|
47 |
+(defmethod yason:encode ((item alimenta:item) &optional stream) |
|
48 |
+ (yason:with-output (stream :indent t) |
|
49 |
+ (yason:encode-slots item)) |
|
47 | 50 |
item) |
48 | 51 |
|