;;;;granfalloon.lisp

;; Doesn't need to be on your computer in order for you to use it.
;; In this contingency, recite the following line of prayer (semicolon omitted)
;printf "\n" | nc sdf.org 70
;; And repeat, following the prompts and referring to RFC1436.

(defpackage granfalloon (:nicknames gfln) (:use cl cl-user)
                       (:export nl bind-nl nconc-nls plain))
(in-package granfalloon)

(defclass ext-proc ()
((2way :initarg 2way :accessor 2way)
 (proc :initarg proc :accessor proc)))

(defclass nc (ext-proc) ())

(defmethod shared-initialize :after ((obj nc) names &key address port &allow-other-keys) "
Instantiates obj based on :address and :port.
"
(declare (ignore names args))
(multiple-value-bind (2way stat proc)
 (ext:run-program "nc" `(,address ,port) :wait nil)
 (assert (null stat))
 (setf (2way obj) 2way (proc obj) proc)))

(defun visit (address &optional (port 70)) "
(visit address &optional (port 70))
For internal use.
Instantiates a nc process with the address and port.
Returns a closure which consumes an item-specifier.
Consider using bind-nl instead.
"
(let* ((nc (make-instance 'nc :allow-other-keys t :address address :port (format nil "~d" port)))
       (in (two-way-stream-input-stream (2way nc)))
       (out (two-way-stream-output-stream (2way nc))))
 (labels ((readline-stat () (handler-case
                             (values (let ((ch (read-char-no-hang in)))
                                       (when ch
                                        (with-output-to-string (*standard-output*)
                                         (princ ch) (princ (read-line in)))))
                                     (ext:external-process-status (proc nc)))
                             (end-of-file (print "eof") (terpri))))
          (specify-item (item-specifier)
           (format out "~a~%" item-specifier)
           (force-output out)
           #'readline-stat))
  (values #'specify-item))))


(defvar *nl*)
(defun bind-nl (&key (address "sdf.org") (port 70) (spec "")) "
Despite its name,
bind-nl (&key (address \"sdf.org\") (port 70) (spec \"\"))
Setfs (symbol-function '*nl*)
to (funcall (visit address port) spec)
Thereafter,
(*nl*) -> (values (when line) ext-stat)
For some reasonable meanings of those values.
ext-stat is probably :running but might not be.
"
(let* ((item-specifier (visit address port))
       (readline-stat (funcall item-specifier spec)))
 (setf (symbol-function '*nl*) readline-stat)))

(defun plain (list &optional (stream t)) "
plain (list &optional (stream t))
Princs a list line by line.
"
(format stream "~{~a~^~%~}~%" list))

(defun nconc-nls (&optional list) "
nconc-nls (&optional list)
Calls *nl* to get lines. With a slow enough connection and fast fingers,
this might need to be called multiple times to finish reading an item.
This is like this because I found some servers don't close gopher connections.
If called with the optional list, nconcs to it.
"
(nconc list (loop for line = (*nl*) while line collect line)))

(defmacro -q (&body body) "
-q (&body body)
Always returns nil and squelches *standard-output* of body.
"
`(prog1 (values) (with-output-to-string (*standard-output*) ,@body)))