zone.lisp: Refactor the output stage.
authorMark Wooding <mdw@distorted.org.uk>
Wed, 30 Apr 2014 15:12:23 +0000 (16:12 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Wed, 30 Apr 2014 23:48:25 +0000 (00:48 +0100)
Provide a default implementation of `zone-write', which does the tedious
business of iterating over the individual records.  Make the output
formats use this new protocol.

frontend.lisp
zone.lisp

index cb22d3e..0764c3f 100644 (file)
                           (keyword opt-format
                                    (delete-duplicates
                                     (loop for method in
-                                          (generic-function-methods
-                                           #'zone:zone-write)
+                                          (append
+                                           (generic-function-methods
+                                            #'zone:zone-write)
+                                           (generic-function-methods
+                                            #'zone:zone-write-header))
                                           for specs =
                                           (method-specializers method)
                                           if (typep (car specs)
index c586915..32f1aed 100644 (file)
--- a/zone.lisp
+++ b/zone.lisp
 (defvar *zone-output-stream* nil
   "Stream to write zone data on.")
 
-(defmethod zone-write :around (format zone stream)
-  (declare (ignore format))
+(defgeneric zone-write-raw-rrdata (format zr type data)
+  (:documentation "Write an otherwise unsupported record in a given FORMAT.
+
+   ZR gives the record object, which carries the name and TTL; the TYPE is
+   the numeric RRTYPE code; and DATA is an octet vector giving the RRDATA.
+   This is used by the default `zone-write-record' method to handle record
+   types which aren't directly supported by the format driver."))
+
+(export 'zone-write-header)
+(defgeneric zone-write-header (format zone)
+  (:documentation "Emit the header for a ZONE, in a given FORMAT.
+
+   The header includes any kind of initial comment, the SOA record, and any
+   other necessary preamble.  There is no default implementation.
+
+   This is part of the protocol used by the default method on `zone-write';
+   if you override that method."))
+
+(export 'zone-write-trailer)
+(defgeneric zone-write-trailer (format zone)
+  (:documentation "Emit the header for a ZONE, in a given FORMAT.
+
+   The footer may be empty, and is so by default.
+
+   This is part of the protocol used by the default method on `zone-write';
+   if you override that method.")
+  (:method (format zone)
+    (declare (ignore format zone))
+    nil))
+
+(export 'zone-write-record)
+(defgeneric zone-write-record (format type zr)
+  (:documentation "Emit a record of the given TYPE (a keyword).
+
+   There is no default implementation."))
+
+(defmethod zone-write (format zone stream)
+  "This default method calls `zone-write-header', then `zone-write-record'
+   for each record in the zone, and finally `zone-write-trailer'.  While it's
+   running, `*writing-zone*' is bound to the zone object, and
+  `*zone-output-stream*' to the output stream."
   (let ((*writing-zone* zone)
        (*zone-output-stream* stream))
-    (call-next-method)))
+    (zone-write-header format zone)
+    (dolist (zr (zone-records-sorted zone))
+      (zone-write-record format (zr-type zr) zr))
+    (zone-write-trailer format zone)))
 
 (export 'zone-save)
 (defun zone-save (zones &key (format :bind))
           (setf *bind-last-record-name* name)
           name))))
 
-(export 'bind-record)
-(defgeneric bind-record (type zr))
+(defmethod zone-write :around ((format (eql :bind)) zone stream)
+  (let ((*bind-last-record-name* nil))
+    (call-next-method)))
 
-(defmethod zone-write ((format (eql :bind)) zone stream)
-  (format stream "~
+(defmethod zone-write-header ((format (eql :bind)) zone)
+  (format *zone-output-stream* "~
 ;;; Zone file `~(~A~)'
 ;;;   (generated ~A)
 
@@ -1126,15 +1169,14 @@ $TTL ~2@*~D~2%"
            (zone-name zone)
            (iso-date :now :datep t :timep t)
            (zone-default-ttl zone))
-  (let* ((*bind-last-record-name* nil)
-        (soa (zone-soa zone))
+  (let* ((soa (zone-soa zone))
         (admin (let* ((name (soa-admin soa))
                       (at (position #\@ name))
                       (copy (format nil "~(~A~)." name)))
                  (when at
                    (setf (char copy at) #\.))
                  copy)))
-      (format stream "~
+      (format *zone-output-stream* "~
 ~A~30TIN SOA~40T~A (
 ~55@A~60T ;administrator
 ~45T~10D~60T ;serial
@@ -1149,49 +1191,49 @@ $TTL ~2@*~D~2%"
              (soa-refresh soa)
              (soa-retry soa)
              (soa-expire soa)
-             (soa-min-ttl soa))
-      (dolist (zr (zone-records-sorted zone))
-       (bind-record (zr-type zr) zr))))
+             (soa-min-ttl soa))))
 
 (export 'bind-format-record)
-(defun bind-format-record (name ttl type format args)
+(defun bind-format-record (zr format &rest args)
   (format *zone-output-stream*
          "~A~20T~@[~8D~]~30TIN ~A~40T~?~%"
-         (bind-output-hostname name)
-         (and (/= ttl (zone-default-ttl *writing-zone*))
-              ttl)
-         (string-upcase (symbol-name type))
+         (bind-output-hostname (zr-name zr))
+         (let ((ttl (zr-ttl zr)))
+           (and (/= ttl (zone-default-ttl *writing-zone*))
+                ttl))
+         (string-upcase (symbol-name (zr-type zr)))
          format args))
 
-(export 'bind-record-type)
-(defgeneric bind-record-type (type)
-  (:method (type) type))
-
-(export 'bind-record-format-args)
-(defgeneric bind-record-format-args (type data)
-  (:method ((type (eql :a)) data) (list "~A" (ipaddr-string data)))
-  (:method ((type (eql :aaaa)) data) (list "~A" (ipaddr-string data)))
-  (:method ((type (eql :ptr)) data) (list "~A" (bind-hostname data)))
-  (:method ((type (eql :cname)) data) (list "~A" (bind-hostname data)))
-  (:method ((type (eql :ns)) data) (list "~A" (bind-hostname data)))
-  (:method ((type (eql :mx)) data)
-    (list "~2D ~A" (cdr data) (bind-hostname (car data))))
-  (:method ((type (eql :srv)) data)
-    (destructuring-bind (prio weight port host) data
-      (list "~2D ~5D ~5D ~A" prio weight port (bind-hostname host))))
-  (:method ((type (eql :sshfp)) data)
-    (cons "~2D ~2D ~A" data))
-  (:method ((type (eql :txt)) data)
-    (cons "~#[\"\"~;~S~:;(~@{~%~8T~S~} )~]"
-         (mapcar #'stringify data))))
-
-(defmethod bind-record (type zr)
-  (destructuring-bind (format &rest args)
-      (bind-record-format-args type (zr-data zr))
-    (bind-format-record (zr-name zr)
-                       (zr-ttl zr)
-                       (bind-record-type type)
-                       format args)))
+(defmethod zone-write-record ((format (eql :bind)) (type (eql :a)) zr)
+  (bind-format-record zr "~A" (ipaddr-string (zr-data zr))))
+
+(defmethod zone-write-record ((format (eql :bind)) (type (eql :aaaa)) zr)
+  (bind-format-record zr "~A" (ipaddr-string (zr-data zr))))
+
+(defmethod zone-write-record ((format (eql :bind)) (type (eql :ptr)) zr)
+  (bind-format-record zr "~A" (bind-hostname (zr-data zr))))
+
+(defmethod zone-write-record ((format (eql :bind)) (type (eql :cname)) zr)
+  (bind-format-record zr "~A" (bind-hostname (zr-data zr))))
+
+(defmethod zone-write-record ((format (eql :bind)) (type (eql :ns)) zr)
+  (bind-format-record zr "~A" (bind-hostname (zr-data zr))))
+
+(defmethod zone-write-record ((format (eql :bind)) (type (eql :mx)) zr)
+  (bind-format-record zr "~2D ~A"
+                     (cdr (zr-data zr))
+                     (bind-hostname (car (zr-data zr)))))
+
+(defmethod zone-write-record ((format (eql :bind)) (type (eql :srv)) zr)
+  (destructuring-bind (prio weight port host) (zr-data zr)
+    (bind-format-record zr "~2D ~5D ~5D ~A"
+                       prio weight port (bind-hostname host))))
+
+(defmethod zone-write-record ((format (eql :bind)) (type (eql :sshfp)) zr)
+  (bind-format-record zr "~{~2D ~2D ~A~}" (zr-data zr)))
+
+(defmethod zone-write-record ((format (eql :bind)) (type (eql :txt)) zr)
+  (bind-format-record zr "~{~#[\"\"~;~S~:;(~@{~%~8T~S~} )~]~}" (zr-data zr)))
 
 ;;;--------------------------------------------------------------------------
 ;;; tinydns-data output format.
@@ -1211,53 +1253,60 @@ $TTL ~2@*~D~2%"
                            (write-char (code-char byte) out)))))
                  (zr-ttl zr)))
 
-(defgeneric tinydns-record (type zr)
-  (:method ((type (eql :a)) zr)
-    (tinydns-output #\+ (zr-name zr)
-                   (ipaddr-string (zr-data zr)) (zr-ttl zr)))
-  (:method ((type (eql :aaaa)) zr)
-    (tinydns-output #\3 (zr-name zr)
-                   (format nil "~(~32,'0X~)" (ipaddr-addr (zr-data zr)))
-                   (zr-ttl zr)))
-  (:method ((type (eql :ptr)) zr)
-    (tinydns-output #\^ (zr-name zr) (zr-data zr) (zr-ttl zr)))
-  (:method ((type (eql :cname)) zr)
-    (tinydns-output #\C (zr-name zr) (zr-data zr) (zr-ttl zr)))
-  (:method ((type (eql :ns)) zr)
-    (tinydns-output #\& (zr-name zr) nil (zr-data zr) (zr-ttl zr)))
-  (:method ((type (eql :mx)) zr)
-    (let ((name (car (zr-data zr)))
-         (prio (cdr (zr-data zr))))
-      (tinydns-output #\@ (zr-name zr) nil name prio (zr-ttl zr))))
-  (:method ((type (eql :txt)) zr)
-    (tinydns-raw-record 16 zr
+(defmethod zone-write-record ((format (eql :tinydns)) (type (eql :a)) zr)
+  (tinydns-output #\+ (zr-name zr)
+                 (ipaddr-string (zr-data zr)) (zr-ttl zr)))
+
+(defmethod zone-write-record ((format (eql :tinydns)) (type (eql :aaaa)) zr)
+  (tinydns-output #\3 (zr-name zr)
+                 (format nil "~(~32,'0X~)" (ipaddr-addr (zr-data zr)))
+                 (zr-ttl zr)))
+
+(defmethod zone-write-record ((format (eql :tinydns)) (type (eql :ptr)) zr)
+  (tinydns-output #\^ (zr-name zr) (zr-data zr) (zr-ttl zr)))
+
+(defmethod zone-write-record ((format (eql :tinydns)) (type (eql :cname)) zr)
+  (tinydns-output #\C (zr-name zr) (zr-data zr) (zr-ttl zr)))
+
+(defmethod zone-write-record ((format (eql :tinydns)) (type (eql :ns)) zr)
+  (tinydns-output #\& (zr-name zr) nil (zr-data zr) (zr-ttl zr)))
+
+(defmethod zone-write-record ((format (eql :tinydns)) (type (eql :mx)) zr)
+  (let ((name (car (zr-data zr)))
+       (prio (cdr (zr-data zr))))
+    (tinydns-output #\@ (zr-name zr) nil name prio (zr-ttl zr))))
+
+(defmethod zone-write-record ((format (eql :tinydns)) (type (eql :txt)) zr)
+  (tinydns-raw-record 16 zr
+                     (build-record
+                       (dolist (s (zr-data zr))
+                         (rec-u8 (length s))
+                         (rec-raw-string s)))))
+
+(defmethod zone-write-record ((format (eql :tinydns)) (type (eql :srv)) zr)
+  (destructuring-bind (prio weight port host) (zr-data zr)
+    (tinydns-raw-record 33 zr
+                       (build-record
+                         (rec-u16 prio)
+                         (rec-u16 weight)
+                         (rec-u16 port)
+                         (rec-name host)))))
+
+(defmethod zone-write-record ((format (eql :tinydns)) (type (eql :sshfp)) zr)
+  (destructuring-bind (alg type fpr) (zr-data zr)
+    (tinydns-raw-record 44 zr
                        (build-record
-                         (dolist (s (zr-data zr))
-                           (rec-u8 (length s))
-                           (rec-raw-string s)))))
-  (:method ((type (eql :srv)) zr)
-    (destructuring-bind (prio weight port host) (zr-data zr)
-      (tinydns-raw-record 33 zr
-                         (build-record
-                           (rec-u16 prio)
-                           (rec-u16 weight)
-                           (rec-u16 port)
-                           (rec-name host)))))
-  (:method ((type (eql :sshfp)) zr)
-    (destructuring-bind (alg type fpr) (zr-data zr)
-      (tinydns-raw-record 44 zr
-                         (build-record
-                           (rec-u8 alg)
-                           (rec-u8 type)
-                           (do ((i 0 (+ i 2))
-                                (n (length fpr)))
-                               ((>= i n))
-                             (rec-u8 (parse-integer fpr
-                                                    :start i :end (+ i 2)
-                                                    :radix 16))))))))
-
-(defmethod zone-write ((format (eql :tinydns)) zone stream)
-  (format stream "~
+                         (rec-u8 alg)
+                         (rec-u8 type)
+                         (do ((i 0 (+ i 2))
+                              (n (length fpr)))
+                             ((>= i n))
+                           (rec-u8 (parse-integer fpr
+                                                  :start i :end (+ i 2)
+                                                  :radix 16))))))))
+
+(defmethod zone-write-header ((format (eql :tinydns)) zone)
+  (format *zone-output-stream* "~
 ### Zone file `~(~A~)'
 ###   (generated ~A)
 ~%"
@@ -1275,8 +1324,6 @@ $TTL ~2@*~D~2%"
                    (soa-refresh soa)
                    (soa-expire soa)
                    (soa-min-ttl soa)
-                   (zone-default-ttl zone)))
-  (dolist (zr (zone-records-sorted zone))
-    (tinydns-record (zr-type zr) zr)))
+                   (zone-default-ttl zone))))
 
 ;;;----- That's all, folks --------------------------------------------------