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