git.fiddlerwoaroof.com
Raw Blame History
(in-package :cl-user)
(defpackage cl-yaml-test.round-trip
  (:use :cl :fiveam)
  (:export :round-trip)
  (:documentation "Round-trip Emitter/Parser tests."))
(in-package :cl-yaml-test.round-trip)

(def-suite round-trip
    :description "YAML emitter/parser tests.")
(in-suite round-trip)

(defclass invoice ()
  ((number :initarg :number :accessor invoice-number)
   (date :initarg :date :accessor invoice-date)
   (bill-to :initarg :bill-to :accessor invoice-bill-to)
   ;; skip ship-to until aliases and anchors are more developed)
   (product :initarg :product :accessor invoice-product)
   (tax :initarg :tax :accessor invoice-tax)
   (total :initarg :total :accessor invoice-total)
   (comments :initarg :comments :accessor invoice-comments)))

(defclass address ()
  ((lines :initarg :lines :accessor address-lines)
   (city :initarg :city :accessor address-city)
   (state :initarg :state :accessor address-state)
   (postal :initarg :postal :accessor address-postal)))

(defclass product-order ()
  ((sku :initarg :sku :accessor product-order-sku)
   (quantity :initarg :quantity :accessor product-order-quantity)
   (description :initarg :description :accessor product-order-description)
   (price :initarg :price :accessor product-order-price)))

(defun make-test-invoice ()
  (make-instance 'invoice
		 :number 34843
		 :date "2001-01-23"
		 :bill-to (list "Chris" "Dumars"
				(make-instance 'address
					       :lines "458 Walkman Dr.
Suite #292"
					       :city "Royal Oak"
					       :state "MI"
					       :postal "48046"))
		 :product (list (make-instance 'product-order
					       :sku "BL394D"
					       :quantity 4
					       :description "Basketball"
					       :price 450.00)
				(make-instance 'product-order
					       :sku "BL4438H"
					       :quantity 1
					       :description "Super Hoop"
					       :price 2392.00))
		 :tax 251.42
		 :total 4443.52
		 :comments "Late afternoon is best. Backup contact is Nancy Billsmer @ 338-4338."))

;;; Methods to emit CLOS objects

(defmethod yaml.emitter:emit-object (emitter (obj address))
  (yaml.emitter:emit-mapping (emitter :style :block-mapping-style)
    (yaml.emitter:emit-scalar emitter "lines")
    (yaml.emitter:emit-scalar emitter (address-lines obj)
			      :style :literal-scalar-style)
    (yaml.emitter:emit-scalar emitter "city")
    (yaml.emitter:emit-scalar emitter (address-city obj))
    (yaml.emitter:emit-scalar emitter "state")
    (yaml.emitter:emit-scalar emitter (address-state obj))
    (yaml.emitter:emit-scalar emitter "postal")
    (yaml.emitter:emit-scalar emitter (address-postal obj))))

(defmethod yaml.emitter:emit-object (emitter (obj product-order))
  (yaml.emitter:emit-mapping (emitter :style :block-mapping-style)
    (yaml.emitter:emit-scalar emitter "sku")
    (yaml.emitter:emit-scalar emitter (product-order-sku obj))
    (yaml.emitter:emit-scalar emitter "quantity")
    (yaml.emitter:emit-scalar emitter (product-order-quantity obj))
    (yaml.emitter:emit-scalar emitter "description")
    (yaml.emitter:emit-scalar emitter (product-order-description obj))
    (yaml.emitter:emit-scalar emitter "price")
    (yaml.emitter:emit-scalar emitter (product-order-price obj))))

(defmethod yaml.emitter:emit-object (emitter (obj invoice))
  (yaml.emitter:emit-mapping (emitter :style :block-mapping-style
				      :tag "clarkevans.com,2002:invoice")
    (yaml.emitter:emit-scalar emitter "invoice")
    (yaml.emitter:emit-scalar emitter (invoice-number obj))
    (yaml.emitter:emit-scalar emitter "date")
    (yaml.emitter:emit-scalar emitter (invoice-date obj))
    (yaml.emitter:emit-scalar emitter "bill-to")
    (yaml.emitter:emit-mapping (emitter :style :block-mapping-style)
      (yaml.emitter:emit-scalar emitter "given")
      (yaml.emitter:emit-scalar emitter (first (invoice-bill-to obj)))
      (yaml.emitter:emit-scalar emitter "family")
      (yaml.emitter:emit-scalar emitter (second (invoice-bill-to obj)))
      (yaml.emitter:emit-scalar emitter "address")
      (yaml.emitter:emit-object emitter (third (invoice-bill-to obj))))
    (yaml.emitter:emit-scalar emitter "product")
    (yaml.emitter:emit-sequence (emitter :style :block-sequence-style)
      (dolist (product (invoice-product obj))
	(yaml.emitter:emit-object emitter product)))
    (yaml.emitter:emit-scalar emitter "tax")
    (yaml.emitter:emit-scalar emitter (invoice-tax obj))
    (yaml.emitter:emit-scalar emitter "total")
    (yaml.emitter:emit-scalar emitter (invoice-total obj))
    (yaml.emitter:emit-scalar emitter "comments")
    (yaml.emitter:emit-scalar emitter (invoice-comments obj)
			      :plain-implicit t
			      :quoted-implicit t
			      :style :plain-scalar-style)))

;;; Functions to construct objects from YAML

(defun construct-address (mapping)
  (make-instance 'address
		 :lines (gethash "lines" mapping)
		 :city (gethash "city" mapping)
		 :state (gethash "state" mapping)
		 :postal (gethash "postal" mapping)))

(defun construct-product-order (mapping)
  (make-instance 'product-order
		 :sku (gethash "sku" mapping)
		 :quantity (gethash "quantity" mapping)
		 :description (gethash "description" mapping)
		 :price (gethash "price" mapping)))

(defun construct-invoice (mapping)
  (let ((bill-to-mapping (gethash "bill-to" mapping)))
    (make-instance 'invoice
		   :number (gethash "invoice" mapping)
		   :date (gethash "date" mapping)
		   :bill-to (list (gethash "given" bill-to-mapping)
				  (gethash "family" bill-to-mapping)
				  (construct-address (gethash "address" bill-to-mapping)))
		   :product (mapcar #'construct-product-order
				    (gethash "product" mapping))
		   :tax (gethash "tax" mapping)
		   :total (gethash "total" mapping)
		   :comments (gethash "comments" mapping))))

;;; Register a construction function with a YAML tag

(eval-when (:load-toplevel)
  (yaml.parser:register-mapping-converter "clarkevans.com,2002:invoice"
					  #'construct-invoice))

;;; Helper

(defun test-emit (obj)
  (yaml.emitter:with-emitter-to-string (emitter)
    (yaml.emitter:emit-stream (emitter)
      (yaml.emitter:emit-document (emitter :implicit t)
	(yaml.emitter:emit-object emitter obj)))))

;;; Round-trip test

(test invoice
  (let* ((invoice-a (make-test-invoice))
	 (yaml-a (test-emit invoice-a))
	 (invoice-b (yaml:parse yaml-a))
	 (yaml-b (test-emit invoice-b)))
    (is (string= yaml-a yaml-b))))