Browse code
Parser interface for constructing object. New emitter interface
Jason Melbye authored on 18/11/2016 04:37:17
Showing 6 changed files
Showing 6 changed files
... | ... |
@@ -1,70 +1,228 @@ |
1 | 1 |
(in-package :cl-user) |
2 | 2 |
(defpackage yaml.emitter |
3 | 3 |
(:use :cl) |
4 |
- (:export :encode |
|
5 |
- :emit |
|
6 |
- :emit-to-string) |
|
4 |
+ (:import-from :cffi |
|
5 |
+ :foreign-free |
|
6 |
+ :null-pointer) |
|
7 |
+ (:import-from :libyaml.emitter |
|
8 |
+ :allocate-emitter |
|
9 |
+ :emitter-initialize |
|
10 |
+ :emitter-delete |
|
11 |
+ :emit |
|
12 |
+ :set-output |
|
13 |
+ :stream-start-event-initialize |
|
14 |
+ :stream-end-event-initialize |
|
15 |
+ :document-start-event-initialize |
|
16 |
+ :document-end-event-initialize |
|
17 |
+ :scalar-event-initialize |
|
18 |
+ :sequence-start-event-initialize |
|
19 |
+ :sequence-end-event-initialize |
|
20 |
+ :mapping-start-event-initialize |
|
21 |
+ :mapping-end-event-initialize) |
|
22 |
+ (:import-from :libyaml.event |
|
23 |
+ :allocate-event |
|
24 |
+ :event-delete) |
|
25 |
+ (:import-from :libyaml.write-handler |
|
26 |
+ :*write-handler-callback* |
|
27 |
+ :*write-handler-stream*) |
|
28 |
+ (:export :stream-start-event |
|
29 |
+ :stream-end-event |
|
30 |
+ :document-start-event |
|
31 |
+ :document-end-event |
|
32 |
+ :scalar-event |
|
33 |
+ :sequence-start-event |
|
34 |
+ :sequence-end-event |
|
35 |
+ :mapping-start-event |
|
36 |
+ :mapping-end-event |
|
37 |
+ :emit-stream |
|
38 |
+ :emit-document |
|
39 |
+ :emit-sequence |
|
40 |
+ :emit-mapping |
|
41 |
+ :emit-scalar |
|
42 |
+ :emit-object |
|
43 |
+ :print-scalar |
|
44 |
+ :with-emitter-to-stream |
|
45 |
+ :with-emitter-to-string) |
|
7 | 46 |
(:documentation "The YAML emitter.")) |
8 | 47 |
(in-package :yaml.emitter) |
9 | 48 |
|
10 |
-;;; Encoder functions |
|
11 |
- |
|
12 |
-(defgeneric encode (value stream) |
|
13 |
- (:documentation "Write the YAML corresponding to value to a stream.")) |
|
14 |
- |
|
15 |
-(defmethod encode ((true (eql 't)) stream) |
|
16 |
- "Encode true." |
|
17 |
- (write-string "true" stream)) |
|
18 |
- |
|
19 |
-(defmethod encode ((true (eql 'nil)) stream) |
|
20 |
- "Encode false." |
|
21 |
- (write-string "false" stream)) |
|
22 |
- |
|
23 |
-(defmethod encode ((integer integer) stream) |
|
24 |
- "Encode an integer." |
|
25 |
- (princ integer stream)) |
|
26 |
- |
|
27 |
-(defmethod encode ((float float) stream) |
|
28 |
- "Encode a float." |
|
29 |
- (princ float stream)) |
|
30 |
- |
|
31 |
-(defmethod encode ((string string) stream) |
|
32 |
- "Encode a string." |
|
33 |
- ;; (write-string string stream) |
|
34 |
- (format stream "~s" string)) |
|
35 |
- |
|
36 |
-(defmethod encode ((list list) stream) |
|
37 |
- "Encode a list." |
|
38 |
- (write-string "[" stream) |
|
39 |
- (loop for sublist on list do |
|
40 |
- (encode (first sublist) stream) |
|
41 |
- (when (rest sublist) |
|
42 |
- (write-string ", " stream))) |
|
43 |
- (write-string "]" stream)) |
|
44 |
- |
|
45 |
-(defmethod encode ((vector vector) stream) |
|
46 |
- "Encode a vector." |
|
47 |
- (encode (loop for elem across vector collecting elem) stream)) |
|
48 |
- |
|
49 |
-(defmethod encode ((table hash-table) stream) |
|
50 |
- "Encode a hash table." |
|
51 |
- (write-string "{ " stream) |
|
52 |
- (loop for sublist on (alexandria:hash-table-keys table) do |
|
53 |
- (let ((key (first sublist))) |
|
54 |
- (encode key stream) |
|
55 |
- (write-string ": " stream) |
|
56 |
- (encode (gethash key table) stream) |
|
57 |
- (when (rest sublist) |
|
58 |
- (write-string ", " stream)))) |
|
59 |
- (write-string " }" stream)) |
|
49 |
+;;; Wrappers around cl-libyaml event interface with defaults and keyword args |
|
60 | 50 |
|
61 |
-;;; Interface |
|
51 |
+(defun stream-start-event (event &key (encoding :utf8-encoding)) |
|
52 |
+ (stream-start-event-initialize event encoding)) |
|
53 |
+ |
|
54 |
+(defun stream-end-event (event) |
|
55 |
+ (stream-end-event-initialize event)) |
|
56 |
+ |
|
57 |
+(defun document-start-event (event &key (version-directive (null-pointer)) |
|
58 |
+ (tag-directive-start (null-pointer)) |
|
59 |
+ (tag-directive-end (null-pointer)) |
|
60 |
+ (implicit nil)) |
|
61 |
+ (document-start-event-initialize event version-directive |
|
62 |
+ tag-directive-start |
|
63 |
+ tag-directive-end |
|
64 |
+ implicit)) |
|
65 |
+ |
|
66 |
+(defun document-end-event (event &key (implicit nil)) |
|
67 |
+ (document-end-event-initialize event implicit)) |
|
68 |
+ |
|
69 |
+(defun sequence-start-event (event &key (anchor (null-pointer)) |
|
70 |
+ (tag (null-pointer)) |
|
71 |
+ (implicit nil) |
|
72 |
+ (style :any-sequence-style)) |
|
73 |
+ (sequence-start-event-initialize event anchor tag implicit style)) |
|
74 |
+ |
|
75 |
+(defun sequence-end-event (event) |
|
76 |
+ (sequence-end-event-initialize event)) |
|
77 |
+ |
|
78 |
+(defun mapping-start-event (event &key (anchor (null-pointer)) |
|
79 |
+ (tag (null-pointer)) |
|
80 |
+ (implicit nil) |
|
81 |
+ (style :any-mapping-style)) |
|
82 |
+ (mapping-start-event-initialize event anchor tag implicit style)) |
|
83 |
+ |
|
84 |
+(defun mapping-end-event (event) |
|
85 |
+ (mapping-end-event-initialize event)) |
|
86 |
+ |
|
87 |
+(defun scalar-event (event value length &key (anchor (null-pointer)) |
|
88 |
+ (tag (null-pointer)) |
|
89 |
+ (plain-implicit t) |
|
90 |
+ (quoted-implicit t) |
|
91 |
+ (style :plain-scalar-style)) |
|
92 |
+ (scalar-event-initialize event anchor tag value length |
|
93 |
+ plain-implicit quoted-implicit style)) |
|
94 |
+ |
|
95 |
+;;; Emitter macros and output functions |
|
96 |
+ |
|
97 |
+;;; When passing a foreign emitter object, it is also paired with a |
|
98 |
+;;; foreign event object. |
|
99 |
+ |
|
100 |
+(defun foreign-emitter (emitter) (car emitter)) |
|
101 |
+ |
|
102 |
+(defun foreign-event (emitter) (cdr emitter)) |
|
103 |
+ |
|
104 |
+(defmacro with-emitter-to-stream ((emitter-var output-stream) &rest body) |
|
105 |
+ (let ((foreign-emitter (gensym "EMITTER")) |
|
106 |
+ (foreign-event (gensym "EVENT"))) |
|
107 |
+ `(let* ((,foreign-emitter (allocate-emitter)) |
|
108 |
+ (,foreign-event (allocate-event)) |
|
109 |
+ (,emitter-var (cons ,foreign-emitter ,foreign-event)) |
|
110 |
+ (*write-handler-stream* ,output-stream)) |
|
111 |
+ (unwind-protect |
|
112 |
+ (progn |
|
113 |
+ (emitter-initialize ,foreign-emitter) |
|
114 |
+ (set-output ,foreign-emitter *write-handler-callback* (null-pointer)) |
|
115 |
+ ,@body) |
|
116 |
+ (libyaml.event:event-delete ,foreign-event) |
|
117 |
+ (libyaml.emitter:emitter-delete ,foreign-emitter) |
|
118 |
+ (foreign-free ,foreign-event) |
|
119 |
+ (foreign-free ,foreign-emitter))))) |
|
120 |
+ |
|
121 |
+(defmacro with-emitter-to-string ((emitter-var) &rest body) |
|
122 |
+ (let ((str (gensym "STR"))) |
|
123 |
+ `(with-output-to-string (,str) |
|
124 |
+ (with-emitter-to-stream (,emitter-var ,str) |
|
125 |
+ ,@body)))) |
|
62 | 126 |
|
63 |
-(defun emit (value stream) |
|
64 |
- "Emit a value to a stream." |
|
65 |
- (encode value stream)) |
|
127 |
+(defmacro emit-stream ((emitter &key (encoding :utf8-encoding)) &body body) |
|
128 |
+ (let ((emitter-value (gensym "EMITTER")) |
|
129 |
+ (foreign-emitter (gensym "FOREIGN-EMITTER")) |
|
130 |
+ (foreign-event (gensym "FOREIGN-EVENT"))) |
|
131 |
+ `(let* ((,emitter-value ,emitter) |
|
132 |
+ (,foreign-emitter (foreign-emitter ,emitter-value)) |
|
133 |
+ (,foreign-event (foreign-event ,emitter-value))) |
|
134 |
+ (stream-start-event ,foreign-event :encoding ,encoding) |
|
135 |
+ (libyaml.emitter:emit ,foreign-emitter ,foreign-event) |
|
136 |
+ ,@body |
|
137 |
+ (stream-end-event ,foreign-event) |
|
138 |
+ (libyaml.emitter:emit ,foreign-emitter ,foreign-event)))) |
|
139 |
+ |
|
140 |
+(defmacro emit-document ((emitter &rest rest |
|
141 |
+ &key version-directive |
|
142 |
+ tag-directive-start |
|
143 |
+ tag-directive-end |
|
144 |
+ (implicit 0)) &body body) |
|
145 |
+ (declare (ignorable version-directive tag-directive-start |
|
146 |
+ tag-directive-end implicit)) |
|
147 |
+ (let ((emitter-value (gensym "EMITTER")) |
|
148 |
+ (foreign-emitter (gensym "FOREIGN-EMITTER")) |
|
149 |
+ (foreign-event (gensym "FOREIGN-EVENT"))) |
|
150 |
+ `(let* ((,emitter-value ,emitter) |
|
151 |
+ (,foreign-emitter (foreign-emitter ,emitter-value)) |
|
152 |
+ (,foreign-event (foreign-event ,emitter-value))) |
|
153 |
+ (apply #'document-start-event ,foreign-event (list ,@rest)) |
|
154 |
+ (libyaml.emitter:emit ,foreign-emitter ,foreign-event) |
|
155 |
+ ,@body |
|
156 |
+ (document-end-event ,foreign-event :implicit ,implicit) |
|
157 |
+ (libyaml.emitter:emit ,foreign-emitter ,foreign-event)))) |
|
158 |
+ |
|
159 |
+(defmacro emit-mapping ((emitter &rest rest &key anchor tag implicit style) |
|
160 |
+ &body body) |
|
161 |
+ (declare (ignorable anchor tag implicit style)) |
|
162 |
+ (let ((emitter-value (gensym "EMITTER")) |
|
163 |
+ (foreign-emitter (gensym "FOREIGN-EMITTER")) |
|
164 |
+ (foreign-event (gensym "FOREIGN-EVENT"))) |
|
165 |
+ `(let* ((,emitter-value ,emitter) |
|
166 |
+ (,foreign-emitter (foreign-emitter ,emitter-value)) |
|
167 |
+ (,foreign-event (foreign-event ,emitter-value))) |
|
168 |
+ (apply #'mapping-start-event ,foreign-event (list ,@rest)) |
|
169 |
+ (libyaml.emitter:emit ,foreign-emitter ,foreign-event) |
|
170 |
+ ,@body |
|
171 |
+ (mapping-end-event ,foreign-event) |
|
172 |
+ (libyaml.emitter:emit ,foreign-emitter ,foreign-event)))) |
|
173 |
+ |
|
174 |
+(defmacro emit-sequence ((emitter &rest rest &key anchor tag implicit style) |
|
175 |
+ &body body) |
|
176 |
+ (declare (ignorable anchor tag implicit style)) |
|
177 |
+ (let ((emitter-value (gensym "EMITTER")) |
|
178 |
+ (foreign-emitter (gensym "FOREIGN-EMITTER")) |
|
179 |
+ (foreign-event (gensym "FOREIGN-EVENT"))) |
|
180 |
+ `(let* ((,emitter-value ,emitter) |
|
181 |
+ (,foreign-emitter (foreign-emitter ,emitter-value)) |
|
182 |
+ (,foreign-event (foreign-event ,emitter-value))) |
|
183 |
+ (apply #'sequence-start-event ,foreign-event (list ,@rest)) |
|
184 |
+ (libyaml.emitter:emit ,foreign-emitter ,foreign-event) |
|
185 |
+ ,@body |
|
186 |
+ (sequence-end-event ,foreign-event) |
|
187 |
+ (libyaml.emitter:emit ,foreign-emitter ,foreign-event)))) |
|
188 |
+ |
|
189 |
+(defun emit-scalar (emitter value &rest rest &key anchor tag |
|
190 |
+ plain-implicit |
|
191 |
+ quoted-implicit |
|
192 |
+ style) |
|
193 |
+ (declare (ignorable anchor tag plain-implicit quoted-implicit style)) |
|
194 |
+ (let ((printed-value (print-scalar value))) |
|
195 |
+ (apply #'scalar-event (foreign-event emitter) |
|
196 |
+ printed-value (length printed-value) rest) |
|
197 |
+ (emit (foreign-emitter emitter) (foreign-event emitter)))) |
|
198 |
+ |
|
199 |
+(defgeneric print-scalar (scalar) |
|
200 |
+ (:documentation "Convert a scalar object into its printed representation")) |
|
201 |
+ |
|
202 |
+(defmethod print-scalar ((scalar (eql 't))) |
|
203 |
+ "true") |
|
204 |
+ |
|
205 |
+(defmethod print-scalar ((scalar (eql 'nil))) |
|
206 |
+ "false") |
|
207 |
+ |
|
208 |
+(defmethod print-scalar ((scalar symbol)) |
|
209 |
+ (symbol-name scalar)) |
|
210 |
+ |
|
211 |
+(defmethod print-scalar ((scalar string)) |
|
212 |
+ scalar) |
|
213 |
+ |
|
214 |
+(defmethod print-scalar ((scalar integer)) |
|
215 |
+ (princ-to-string scalar)) |
|
216 |
+ |
|
217 |
+(defmethod print-scalar ((scalar single-float)) |
|
218 |
+ (let ((*read-default-float-format* 'single-float)) |
|
219 |
+ (princ-to-string scalar))) |
|
220 |
+ |
|
221 |
+(defmethod print-scalar ((scalar double-float)) |
|
222 |
+ (let ((*read-default-float-format* 'double-float)) |
|
223 |
+ (princ-to-string scalar))) |
|
224 |
+ |
|
225 |
+;;; Interface |
|
66 | 226 |
|
67 |
-(defun emit-to-string (value) |
|
68 |
- "Emit a value to string." |
|
69 |
- (with-output-to-string (stream) |
|
70 |
- (emit value stream))) |
|
227 |
+(defgeneric emit-object (emitter obj) |
|
228 |
+ (:documentation "Emit YAML representation of obj")) |
... | ... |
@@ -3,8 +3,9 @@ |
3 | 3 |
(:use :cl) |
4 | 4 |
(:nicknames :yaml) |
5 | 5 |
(:export :parse |
6 |
- :emit |
|
7 |
- :emit-to-string) |
|
6 |
+ ;:emit |
|
7 |
+ ;:emit-to-string |
|
8 |
+ ) |
|
8 | 9 |
(:documentation "The main YAML interface.")) |
9 | 10 |
(in-package :yaml) |
10 | 11 |
|
... | ... |
@@ -22,8 +23,8 @@ |
22 | 23 |
(parse (uiop:read-file-string input) |
23 | 24 |
:multi-document-p multi-document-p)) |
24 | 25 |
|
25 |
-(defun emit (value stream) |
|
26 |
- (yaml.emitter:emit value stream)) |
|
26 |
+;; (defun emit (value stream) |
|
27 |
+;; (yaml.emitter:emit value stream)) |
|
27 | 28 |
|
28 |
-(defun emit-to-string (value) |
|
29 |
- (yaml.emitter:emit-to-string value)) |
|
29 |
+;; (defun emit-to-string (value) |
|
30 |
+;; (yaml.emitter:emit-to-string value)) |
... | ... |
@@ -1,67 +1,80 @@ |
1 | 1 |
(in-package :cl-user) |
2 | 2 |
(defpackage cl-yaml-test.emitter |
3 | 3 |
(:use :cl :fiveam) |
4 |
- (:import-from :alexandria |
|
5 |
- :alist-hash-table) |
|
6 | 4 |
(:export :emitter) |
7 | 5 |
(:documentation "Emitter tests.")) |
8 | 6 |
(in-package :cl-yaml-test.emitter) |
9 | 7 |
|
10 |
-;;; Macros |
|
11 |
- |
|
12 | 8 |
(defmacro define-test-cases ((name) &rest pairs) |
13 | 9 |
`(test ,name |
14 | 10 |
,@(loop for (form string) in pairs collecting |
15 |
- `(is (equal (yaml.emitter:emit-to-string ,form) |
|
16 |
- ,string))))) |
|
11 |
+ `(is (equal (yaml.emitter:with-emitter-to-string (emitter) |
|
12 |
+ (yaml.emitter:emit-stream (emitter) |
|
13 |
+ (yaml.emitter:emit-document (emitter :implicit t) |
|
14 |
+ (yaml.emitter:emit-scalar emitter ,form)))) |
|
15 |
+ ,(format nil "~a~%...~%" string)))))) ;; Document end marker |
|
16 |
+ ;; is libyaml behavior |
|
17 |
+ |
|
18 |
+(defun test-emit-sequence (sequence style) |
|
19 |
+ (yaml.emitter:with-emitter-to-string (emitter) |
|
20 |
+ (yaml.emitter:emit-stream (emitter) |
|
21 |
+ (yaml.emitter:emit-document (emitter :implicit t) |
|
22 |
+ (yaml.emitter:emit-sequence (emitter :style style) |
|
23 |
+ (mapcar (lambda (element) (yaml.emitter:emit-scalar emitter element)) |
|
24 |
+ sequence)))))) |
|
17 | 25 |
|
18 |
-;;; Tests |
|
26 |
+(defun test-emit-mapping (mapping style) |
|
27 |
+ (yaml.emitter:with-emitter-to-string (emitter) |
|
28 |
+ (yaml.emitter:emit-stream (emitter) |
|
29 |
+ (yaml.emitter:emit-document (emitter :implicit t) |
|
30 |
+ (yaml.emitter:emit-mapping (emitter :style style) |
|
31 |
+ (mapcar (lambda (pair) |
|
32 |
+ (yaml.emitter:emit-scalar emitter (car pair)) |
|
33 |
+ (yaml.emitter:emit-scalar emitter (cdr pair))) |
|
34 |
+ mapping)))))) |
|
19 | 35 |
|
20 | 36 |
(def-suite emitter |
21 |
- :description "YAML emitter tests.") |
|
37 |
+ :description "YAML emitter tests.") |
|
22 | 38 |
(in-suite emitter) |
23 | 39 |
|
24 | 40 |
(define-test-cases (boolean) |
25 |
- (t |
|
26 |
- "true") |
|
27 |
- (nil |
|
28 |
- "false")) |
|
41 |
+ (t "true") |
|
42 |
+ (nil "false")) |
|
29 | 43 |
|
30 | 44 |
(define-test-cases (integers) |
31 |
- (1 |
|
32 |
- "1") |
|
33 |
- (123 |
|
34 |
- "123") |
|
35 |
- (+123 |
|
36 |
- "123") |
|
37 |
- (-123 |
|
38 |
- "-123")) |
|
45 |
+ (1 "1") |
|
46 |
+ (123 "123") |
|
47 |
+ (+123 "123") |
|
48 |
+ (-123 "-123")) |
|
39 | 49 |
|
40 | 50 |
(define-test-cases (floats) |
41 |
- (1.23 |
|
42 |
- "1.23") |
|
43 |
- (6.62607e-34 |
|
44 |
- "6.62607e-34")) |
|
51 |
+ (1.23 "1.23") |
|
52 |
+ (6.62607e-34 "6.62607e-34")) |
|
53 |
+ |
|
54 |
+(test flow-sequence |
|
55 |
+ (is (equal (test-emit-sequence '(1 "a" 3.14f0 3.14d0) :flow-sequence-style) |
|
56 |
+ "[1, a, 3.14, 3.14] |
|
57 |
+"))) |
|
58 |
+ |
|
59 |
+(test block-sequence |
|
60 |
+ (is (equal (test-emit-sequence '(1 "a" 3.14f0 3.14d0) :block-sequence-style) |
|
61 |
+ "- 1 |
|
62 |
+- a |
|
63 |
+- 3.14 |
|
64 |
+- 3.14 |
|
65 |
+"))) |
|
45 | 66 |
|
46 |
-(define-test-cases (lists) |
|
47 |
- ((list 1 2 3) |
|
48 |
- "[1, 2, 3]") |
|
49 |
- ((vector 1 2 3) |
|
50 |
- "[1, 2, 3]")) |
|
67 |
+(test flow-mapping |
|
68 |
+ (is (equal (test-emit-mapping '(("integer" . 1) ("string" . "test") ("bool" . nil)) |
|
69 |
+ :flow-mapping-style) |
|
70 |
+ "{integer: 1, string: test, bool: false} |
|
71 |
+"))) |
|
51 | 72 |
|
52 |
-(test hash-tables |
|
53 |
- (let ((table (alexandria:alist-hash-table |
|
54 |
- (list (cons "a" 1) |
|
55 |
- (cons "b" 2))))) |
|
56 |
- (is |
|
57 |
- (equal (yaml:emit-to-string table) |
|
58 |
- "{ \"b\": 2, \"a\": 1 }")))) |
|
73 |
+(test block-mapping |
|
74 |
+ (is (equal (test-emit-mapping '(("integer" . 1) ("string" . "test") ("bool" . nil)) |
|
75 |
+ :block-mapping-style) |
|
76 |
+ "integer: 1 |
|
77 |
+string: test |
|
78 |
+bool: false |
|
79 |
+"))) |
|
59 | 80 |
|
60 |
-(test toplevel-function |
|
61 |
- (is |
|
62 |
- (equal (yaml:emit-to-string 1) |
|
63 |
- "1")) |
|
64 |
- (is |
|
65 |
- (equal (with-output-to-string (stream) |
|
66 |
- (yaml:emit 1 stream)) |
|
67 |
- "1"))) |
68 | 81 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,165 @@ |
1 |
+(in-package :cl-user) |
|
2 |
+(defpackage cl-yaml-test.round-trip |
|
3 |
+ (:use :cl :fiveam) |
|
4 |
+ (:export :round-trip) |
|
5 |
+ (:documentation "Round-trip Emitter/Parser tests.")) |
|
6 |
+(in-package :cl-yaml-test.round-trip) |
|
7 |
+ |
|
8 |
+(def-suite round-trip |
|
9 |
+ :description "YAML emitter/parser tests.") |
|
10 |
+(in-suite round-trip) |
|
11 |
+ |
|
12 |
+(defclass invoice () |
|
13 |
+ ((number :initarg :number :accessor invoice-number) |
|
14 |
+ (date :initarg :date :accessor invoice-date) |
|
15 |
+ (bill-to :initarg :bill-to :accessor invoice-bill-to) |
|
16 |
+ ;; skip ship-to until aliases and anchors are more developed) |
|
17 |
+ (product :initarg :product :accessor invoice-product) |
|
18 |
+ (tax :initarg :tax :accessor invoice-tax) |
|
19 |
+ (total :initarg :total :accessor invoice-total) |
|
20 |
+ (comments :initarg :comments :accessor invoice-comments))) |
|
21 |
+ |
|
22 |
+(defclass address () |
|
23 |
+ ((lines :initarg :lines :accessor address-lines) |
|
24 |
+ (city :initarg :city :accessor address-city) |
|
25 |
+ (state :initarg :state :accessor address-state) |
|
26 |
+ (postal :initarg :postal :accessor address-postal))) |
|
27 |
+ |
|
28 |
+(defclass product-order () |
|
29 |
+ ((sku :initarg :sku :accessor product-order-sku) |
|
30 |
+ (quantity :initarg :quantity :accessor product-order-quantity) |
|
31 |
+ (description :initarg :description :accessor product-order-description) |
|
32 |
+ (price :initarg :price :accessor product-order-price))) |
|
33 |
+ |
|
34 |
+(defun make-test-invoice () |
|
35 |
+ (make-instance 'invoice |
|
36 |
+ :number 34843 |
|
37 |
+ :date "2001-01-23" |
|
38 |
+ :bill-to (list "Chris" "Dumars" |
|
39 |
+ (make-instance 'address |
|
40 |
+ :lines "458 Walkman Dr. |
|
41 |
+Suite #292" |
|
42 |
+ :city "Royal Oak" |
|
43 |
+ :state "MI" |
|
44 |
+ :postal "48046")) |
|
45 |
+ :product (list (make-instance 'product-order |
|
46 |
+ :sku "BL394D" |
|
47 |
+ :quantity 4 |
|
48 |
+ :description "Basketball" |
|
49 |
+ :price 450.00) |
|
50 |
+ (make-instance 'product-order |
|
51 |
+ :sku "BL4438H" |
|
52 |
+ :quantity 1 |
|
53 |
+ :description "Super Hoop" |
|
54 |
+ :price 2392.00)) |
|
55 |
+ :tax 251.42 |
|
56 |
+ :total 4443.52 |
|
57 |
+ :comments "Late afternoon is best. Backup contact is Nancy Billsmer @ 338-4338.")) |
|
58 |
+ |
|
59 |
+;;; Methods to emit CLOS objects |
|
60 |
+ |
|
61 |
+(defmethod yaml.emitter:emit-object (emitter (obj address)) |
|
62 |
+ (yaml.emitter:emit-mapping (emitter :style :block-mapping-style) |
|
63 |
+ (yaml.emitter:emit-scalar emitter "lines") |
|
64 |
+ (yaml.emitter:emit-scalar emitter (address-lines obj) |
|
65 |
+ :style :literal-scalar-style) |
|
66 |
+ (yaml.emitter:emit-scalar emitter "city") |
|
67 |
+ (yaml.emitter:emit-scalar emitter (address-city obj)) |
|
68 |
+ (yaml.emitter:emit-scalar emitter "state") |
|
69 |
+ (yaml.emitter:emit-scalar emitter (address-state obj)) |
|
70 |
+ (yaml.emitter:emit-scalar emitter "postal") |
|
71 |
+ (yaml.emitter:emit-scalar emitter (address-postal obj)))) |
|
72 |
+ |
|
73 |
+(defmethod yaml.emitter:emit-object (emitter (obj product-order)) |
|
74 |
+ (yaml.emitter:emit-mapping (emitter :style :block-mapping-style) |
|
75 |
+ (yaml.emitter:emit-scalar emitter "sku") |
|
76 |
+ (yaml.emitter:emit-scalar emitter (product-order-sku obj)) |
|
77 |
+ (yaml.emitter:emit-scalar emitter "quantity") |
|
78 |
+ (yaml.emitter:emit-scalar emitter (product-order-quantity obj)) |
|
79 |
+ (yaml.emitter:emit-scalar emitter "description") |
|
80 |
+ (yaml.emitter:emit-scalar emitter (product-order-description obj)) |
|
81 |
+ (yaml.emitter:emit-scalar emitter "price") |
|
82 |
+ (yaml.emitter:emit-scalar emitter (product-order-price obj)))) |
|
83 |
+ |
|
84 |
+(defmethod yaml.emitter:emit-object (emitter (obj invoice)) |
|
85 |
+ (yaml.emitter:emit-mapping (emitter :style :block-mapping-style |
|
86 |
+ :tag "clarkevans.com,2002:invoice") |
|
87 |
+ (yaml.emitter:emit-scalar emitter "invoice") |
|
88 |
+ (yaml.emitter:emit-scalar emitter (invoice-number obj)) |
|
89 |
+ (yaml.emitter:emit-scalar emitter "date") |
|
90 |
+ (yaml.emitter:emit-scalar emitter (invoice-date obj)) |
|
91 |
+ (yaml.emitter:emit-scalar emitter "bill-to") |
|
92 |
+ (yaml.emitter:emit-mapping (emitter :style :block-mapping-style) |
|
93 |
+ (yaml.emitter:emit-scalar emitter "given") |
|
94 |
+ (yaml.emitter:emit-scalar emitter (first (invoice-bill-to obj))) |
|
95 |
+ (yaml.emitter:emit-scalar emitter "family") |
|
96 |
+ (yaml.emitter:emit-scalar emitter (second (invoice-bill-to obj))) |
|
97 |
+ (yaml.emitter:emit-scalar emitter "address") |
|
98 |
+ (yaml.emitter:emit-object emitter (third (invoice-bill-to obj)))) |
|
99 |
+ (yaml.emitter:emit-scalar emitter "product") |
|
100 |
+ (yaml.emitter:emit-sequence (emitter :style :block-sequence-style) |
|
101 |
+ (dolist (product (invoice-product obj)) |
|
102 |
+ (yaml.emitter:emit-object emitter product))) |
|
103 |
+ (yaml.emitter:emit-scalar emitter "tax") |
|
104 |
+ (yaml.emitter:emit-scalar emitter (invoice-tax obj)) |
|
105 |
+ (yaml.emitter:emit-scalar emitter "total") |
|
106 |
+ (yaml.emitter:emit-scalar emitter (invoice-total obj)) |
|
107 |
+ (yaml.emitter:emit-scalar emitter "comments") |
|
108 |
+ (yaml.emitter:emit-scalar emitter (invoice-comments obj) |
|
109 |
+ :plain-implicit t |
|
110 |
+ :quoted-implicit t |
|
111 |
+ :style :plain-scalar-style))) |
|
112 |
+ |
|
113 |
+;;; Functions to construct objects from YAML |
|
114 |
+ |
|
115 |
+(defun construct-address (mapping) |
|
116 |
+ (make-instance 'address |
|
117 |
+ :lines (gethash "lines" mapping) |
|
118 |
+ :city (gethash "city" mapping) |
|
119 |
+ :state (gethash "state" mapping) |
|
120 |
+ :postal (gethash "postal" mapping))) |
|
121 |
+ |
|
122 |
+(defun construct-product-order (mapping) |
|
123 |
+ (make-instance 'product-order |
|
124 |
+ :sku (gethash "sku" mapping) |
|
125 |
+ :quantity (gethash "quantity" mapping) |
|
126 |
+ :description (gethash "description" mapping) |
|
127 |
+ :price (gethash "price" mapping))) |
|
128 |
+ |
|
129 |
+(defun construct-invoice (mapping) |
|
130 |
+ (let ((bill-to-mapping (gethash "bill-to" mapping))) |
|
131 |
+ (make-instance 'invoice |
|
132 |
+ :number (gethash "invoice" mapping) |
|
133 |
+ :date (gethash "date" mapping) |
|
134 |
+ :bill-to (list (gethash "given" bill-to-mapping) |
|
135 |
+ (gethash "family" bill-to-mapping) |
|
136 |
+ (construct-address (gethash "address" bill-to-mapping))) |
|
137 |
+ :product (mapcar #'construct-product-order |
|
138 |
+ (gethash "product" mapping)) |
|
139 |
+ :tax (gethash "tax" mapping) |
|
140 |
+ :total (gethash "total" mapping) |
|
141 |
+ :comments (gethash "comments" mapping)))) |
|
142 |
+ |
|
143 |
+;;; Register a construction function with a YAML tag |
|
144 |
+ |
|
145 |
+(eval-when (:load-toplevel) |
|
146 |
+ (yaml.parser:register-mapping-converter "clarkevans.com,2002:invoice" |
|
147 |
+ #'construct-invoice)) |
|
148 |
+ |
|
149 |
+;;; Helper |
|
150 |
+ |
|
151 |
+(defun test-emit (obj) |
|
152 |
+ (yaml.emitter:with-emitter-to-string (emitter) |
|
153 |
+ (yaml.emitter:emit-stream (emitter) |
|
154 |
+ (yaml.emitter:emit-document (emitter :implicit t) |
|
155 |
+ (yaml.emitter:emit-object emitter obj))))) |
|
156 |
+ |
|
157 |
+;;; Round-trip test |
|
158 |
+ |
|
159 |
+(test invoice |
|
160 |
+ (let* ((invoice-a (make-test-invoice)) |
|
161 |
+ (yaml-a (test-emit invoice-a)) |
|
162 |
+ (invoice-b (yaml:parse yaml-a)) |
|
163 |
+ (yaml-b (test-emit invoice-b))) |
|
164 |
+ (is (string= yaml-a yaml-b)))) |
|
165 |
+ |