;;;;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)))