Browse code
initial commit
fiddlerwoaroof authored on 26/06/2017 16:03:00
Showing 6 changed files
Showing 6 changed files
0 | 2 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,25 @@ |
1 |
+;;;; package.lisp |
|
2 |
+ |
|
3 |
+(in-package :fwoar.sp-user) |
|
4 |
+ |
|
5 |
+(defmacro define-package (name &body arguments) |
|
6 |
+ (let ((uses (cons :use |
|
7 |
+ (union '(:cl :alexandria :serapeum) |
|
8 |
+ (cdr (assoc :use arguments)))))) |
|
9 |
+ `(eval-when (:compile-toplevel :load-toplevel :execute) |
|
10 |
+ (defpackage ,name |
|
11 |
+ ,uses |
|
12 |
+ ,@(remove :use arguments :key 'car))))) |
|
13 |
+ |
|
14 |
+(define-package :stream-provider |
|
15 |
+ (:use :fw.lu) |
|
16 |
+ (:export |
|
17 |
+ #:get-stream-for |
|
18 |
+ #:stream-provider |
|
19 |
+ #:string-provider |
|
20 |
+ #:file-provider |
|
21 |
+ #:stream-key |
|
22 |
+ #:root |
|
23 |
+ #:streams |
|
24 |
+ #:with-storage-stream)) |
|
25 |
+ |
0 | 26 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,35 @@ |
1 |
+;;;; stream-provider.asd |
|
2 |
+ |
|
3 |
+(cl:defpackage :fwoar.sp-system |
|
4 |
+ (:use :cl :asdf)) |
|
5 |
+(in-package :fwoar.sp-system) |
|
6 |
+ |
|
7 |
+(asdf:defsystem #:stream-provider |
|
8 |
+ :description "A class library for interacting with groups of related resources" |
|
9 |
+ :author "Edward Langley <el-streamprovider@elangley.org" |
|
10 |
+ :license "MIT" |
|
11 |
+ :in-order-to ((test-op (test-op "stream-provider.test"))) |
|
12 |
+ :depends-on (#:fwoar.lisputils |
|
13 |
+ #:alexandria |
|
14 |
+ #:serapeum |
|
15 |
+ #:uiop |
|
16 |
+ #:flexi-streams |
|
17 |
+ #:vector-update-stream) |
|
18 |
+ :serial t |
|
19 |
+ :components ((:file "package") |
|
20 |
+ (:file "stream-provider"))) |
|
21 |
+ |
|
22 |
+(asdf:defsystem #:stream-provider.test |
|
23 |
+ :depends-on (#:stream-provider |
|
24 |
+ #:should-test) |
|
25 |
+ :serial t |
|
26 |
+ :components ((:file "package") |
|
27 |
+ (:file "tests")) |
|
28 |
+ :perform (asdf:test-op (o s) |
|
29 |
+ (let ((*package* (find-package :stream-provider.tests))) |
|
30 |
+ (uiop:symbol-call :should-test |
|
31 |
+ :test)))) |
|
32 |
+ |
|
33 |
+(defpackage :fwoar.sp-user |
|
34 |
+ (:use cl) |
|
35 |
+ (:export #:define-package)) |
2 | 38 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,70 @@ |
1 |
+(fwoar.sp-user:define-package :stream-provider |
|
2 |
+ (:use :fw.lu) |
|
3 |
+ (:export #:get-stream-for |
|
4 |
+ #:stream-provider |
|
5 |
+ #:string-provider |
|
6 |
+ #:file-provider |
|
7 |
+ #:stream-key |
|
8 |
+ #:root |
|
9 |
+ #:streams |
|
10 |
+ #:with-storage-stream)) |
|
11 |
+(cl:in-package :stream-provider) |
|
12 |
+ |
|
13 |
+(defclass stream-provider () |
|
14 |
+ ()) |
|
15 |
+ |
|
16 |
+(defvar *store-root* *default-pathname-defaults*) |
|
17 |
+ |
|
18 |
+(defgeneric stream-key (provider item) |
|
19 |
+ (:documentation "return a key for an item: must be able to be compared with EQUAL |
|
20 |
+ |
|
21 |
+If you override on provider, make sure to CALL-NEXT-METHOD")) |
|
22 |
+ |
|
23 |
+(defgeneric get-stream-for (provider streamable &rest extra-args) |
|
24 |
+ (:documentation "get a stream for a given streamable object")) |
|
25 |
+ |
|
26 |
+(defgeneric root (provider) |
|
27 |
+ (:documentation "get the base path for the streams")) |
|
28 |
+ |
|
29 |
+ |
|
30 |
+(defclass string-provider (stream-provider) |
|
31 |
+ ((%streams :reader streams :initform (make-hash-table :test 'equal)))) |
|
32 |
+ |
|
33 |
+(defmethod root ((provider string-provider)) |
|
34 |
+ #p "/") |
|
35 |
+ |
|
36 |
+(defmethod stream-key :around (provider item) |
|
37 |
+ (let ((key (call-next-method))) |
|
38 |
+ (check-type key (or string pathname)) |
|
39 |
+ (uiop:enough-pathname key (root provider)))) |
|
40 |
+ |
|
41 |
+(defmethod get-stream-for ((provider string-provider) streamable &rest extra-args) |
|
42 |
+ (declare (ignore extra-args)) |
|
43 |
+ (with-accessors* (streams) provider |
|
44 |
+ (vector-update-stream:make-update-stream |
|
45 |
+ (setf (gethash (stream-key provider streamable) streams) |
|
46 |
+ (make-array 10 |
|
47 |
+ :element-type 'octet |
|
48 |
+ :adjustable t |
|
49 |
+ :fill-pointer 0))))) |
|
50 |
+ |
|
51 |
+(defclass file-provider (stream-provider) |
|
52 |
+ ((%root :initarg :root :initform (error "need a root for a file-provider") :reader root) |
|
53 |
+ (%if-exists :initarg :if-exists :initform :supersede :reader if-exists))) |
|
54 |
+ |
|
55 |
+(defmethod get-stream-for ((provider file-provider) streamable &rest extra-args) |
|
56 |
+ (declare (ignore extra-args)) |
|
57 |
+ (with-accessors* (if-exists root) provider |
|
58 |
+ (let ((stream-key (merge-pathnames (stream-key provider streamable) |
|
59 |
+ root))) |
|
60 |
+ (when (eql if-exists :if-exists) |
|
61 |
+ (ensure-directories-exist stream-key)) |
|
62 |
+ (open stream-key :direction :output :if-exists if-exists |
|
63 |
+ :element-type 'octet)))) |
|
64 |
+ |
|
65 |
+(defmacro with-storage-stream ((stream-sym object provider &rest extra-args) &body body) |
|
66 |
+ (once-only (object) |
|
67 |
+ `(let ((,stream-sym (flexi-streams:make-flexi-stream (get-stream-for ,provider ,object ,@extra-args) |
|
68 |
+ :external-format :utf-8))) |
|
69 |
+ (unwind-protect (progn ,@body) |
|
70 |
+ (close ,stream-sym))))) |
0 | 71 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,56 @@ |
1 |
+(fwoar.sp-user:define-package :stream-provider.tests |
|
2 |
+ (:use :cl :stream-provider :should-test)) |
|
3 |
+(in-package :stream-provider.tests) |
|
4 |
+ |
|
5 |
+(defparameter *test-string-provider* (make-instance 'string-provider)) |
|
6 |
+(defparameter *test-file-provider* (make-instance 'file-provider :root #p"/nowhere/")) |
|
7 |
+ |
|
8 |
+(defmethod stream-key ((provider (eql *test-string-provider*)) (item string)) |
|
9 |
+ item) |
|
10 |
+ |
|
11 |
+(defmethod stream-key ((provider (eql *test-file-provider*)) (item string)) |
|
12 |
+ item) |
|
13 |
+ |
|
14 |
+(deftest stream-key-returns-specified-pathname () |
|
15 |
+ (let* ((stored-item "foo") |
|
16 |
+ (expected-pathname (pathname "foo"))) |
|
17 |
+ (should be equal |
|
18 |
+ (stream-key *test-string-provider* stored-item) |
|
19 |
+ expected-pathname))) |
|
20 |
+ |
|
21 |
+ |
|
22 |
+ |
|
23 |
+(deftest stream-key-for-file-provider-is-relative-to-root () |
|
24 |
+ (let* ((stored-item "/nowhere/foo") |
|
25 |
+ (expected-pathname (pathname "foo"))) |
|
26 |
+ (should be equal |
|
27 |
+ expected-pathname |
|
28 |
+ (stream-key *test-file-provider* stored-item)))) |
|
29 |
+ |
|
30 |
+(defclass object-to-store () |
|
31 |
+ ((%name :reader name :initarg :name :initform (error "need a name")) |
|
32 |
+ (%value :reader value :initarg :value :initform (error "need a value")))) |
|
33 |
+ |
|
34 |
+(defclass test-provider (string-provider) |
|
35 |
+ ()) |
|
36 |
+ |
|
37 |
+(defmethod stream-key ((provider test-provider) (item object-to-store)) |
|
38 |
+ (name item)) |
|
39 |
+ |
|
40 |
+(defun store (provider item) |
|
41 |
+ (with-storage-stream (s item provider) |
|
42 |
+ (write-sequence (value item) s))) |
|
43 |
+ |
|
44 |
+(deftest with-storage-stream-writes-to-right-place () |
|
45 |
+ (let* ((name "foo") |
|
46 |
+ (value "bar bar") |
|
47 |
+ (object (make-instance 'object-to-store |
|
48 |
+ :name name |
|
49 |
+ :value value)) |
|
50 |
+ (provider (make-instance 'test-provider))) |
|
51 |
+ (should be equal |
|
52 |
+ (store provider object) |
|
53 |
+ value |
|
54 |
+ (babel:octets-to-string |
|
55 |
+ (gethash (stream-key provider object) |
|
56 |
+ (streams provider)))))) |