;;; -*- Mode: Lisp -*- ;;;; New INQUIR - User interface to the LSR1 database of ITS users. ;;; - CStacy, November 1983. ;;; ;;; Replaces awful MIDAS program of the same name. ;;; Includes lots of KMP packages and influence. ;;; ;;; (c) 1983, Massachusetts Institute of Technology. All rights reserved. ;;; Permission to copy all or part of this material is granted, provided ;;; that the copies are not made or distributed for resale, the MIT ;;; copyright notice and reference to the source file and the software ;;; distribution version appear, and that notice is given that copying ;;; is by permission of Massachusetts Institute of Technology. ;Things to do: ; o WHOIS command (FIRST-NAME-FIRST,FIRST-LINE-OF) ; o Add LGOTIM pkg ; o Finish implementing updates propogation feature ; o Inquire reap ; o Stats ;;;; Basic Support and Setup. (herald inquir) ;INQUIR Version. ;There may also be INQFIX files. ;(putprop 'inquir "Experimental" 'release-status) (setq base 10. ibase 10. *nopoint t) ;Reasonable numbers. (sstatus feature noldmsg) ;Quietly. ;;; Macros. (eval-when (eval compile) (if (not (get 'umlmac 'version)) (load '((lisp) umlmac fasl)))) ;;; Lisp Machine compatability. (eval-when (eval compile) (load '((liblsp) lispm fasl))) ;;; KMP's reader and fancy completing command reader. (load '((turnip) reader)) (load '((liblsp) comrd)) ;;; KMP & CSTACY's Fake Strings. (if (not (status feature fake-s)) (load '((spacy) fake-s fasl))) ;;; TTY hackery. (eval-when (eval compile) (load '((liblsp) tty))) ;;; LSR1 database access. (if (not (status feature lsrrtn)) (load '((spacy) lsrrtn fasl))) ;;; HOSTS3 database access. (if (not (status feature netrtn)) (load '((spacy) netrtn fasl))) ;;; LOGOUT TIMES database access. ;(if (not (status feature netrtn)) ; (load '((spacy) lgotim fasl))) ;;; Declarations to keep COMPLR happy. (declare (*lexpr substring string-search string-append terminate-command abort-command bug abort-command-handler flush-command-handler pretty-more-handler flush-inquir change-item verify-item update-one-machine) (*expr string-upcase string-downcase reader *completing-read *completing-read1 char-upcase string string-equal lsritm lsrunm lsrmap lsrnxt lsrdta)) ; host-equal lookup-host ; map-lgotim last-logout)) ;;;; Low-level Lisp extensions. ;;; Macro to define accessor macros for preoperties. (defmacro define-property-cell (name) `(defmacro ,name (x) `(get ,x ',',name))) ;;; Lisp Machine Lisp has this general-purpose ;;; MEMbership testing function. ;;; (MEM #'EQ FOO BAR) <==> (MEMQ FOO BAR) (defun mem (pred item list) (do l list (cdr l) (null l) (and (funcall pred item (car l)) (return l)))) ;;; A limited kind of READ-FROM-STRING. (defun list-from-string (s) (readlist (explodec (string-append "(" s ")" )))) ;;;; The Inquir items. ;;; These are the item numbers defined in and used for LSRRTNs. (declare (special I$UNAM I$NAME I$NICK I$LOCL I$MITA I$MITT I$HOMA I$HOMT I$SUPR I$PROJ I$DIR I$AUTH I$GRP I$REL I$BRTH I$REM I$NETA I$ALTR I$MACH)) ;;; The names of each of the items is a command the user can type. ;;; Asking for HELP on it will tell him the brief command description. ;;; ;;; We store the description of the item on plist of its symbolic name. ;;; The aggregate "current INQUIR entry" we are hacking is ;;; *INQUIR-ITEMS*, a list of the item symbols. (defvar *suname* nil "The UNAME of the current INQUIR entry we are modifying.") (defvar *inquir-items* nil "All the item symbols.") (define-property-cell inquir-value) (define-property-cell i$number) (define-property-cell inqupd-name) (define-property-cell doc) (define-property-cell windy-doc) (define-property-cell max-length) (defmacro def-item (item max-length i$number inqupd-name doc windy-doc) `(progn 'compile (setf (inquir-value ',item) nil) (setf (i$number ',item) ,i$number) (setf (doc ',item) ',doc) (setf (windy-doc ',item) ',windy-doc) (setf (inqupd-name ',item) ,inqupd-name) (setf (max-length ',item) ,max-length) (when (not (member ',item *inquir-items*)) (push ',item *inquir-items*)))) (def-item USER-NAME 6. I$UNAM "UNAME" "User name" "~&What you intend to usually log in as.~ ~%If you don't have one picked out, use your initials.") (def-item AUTHORIZATION 16. I$AUTH "AUTHO" "Authorization" "~&Whether you are a good guy (wear a white hat).") (def-item NAME 40. I$NAME "NAME" "Name" "~&Full name. Last name first, as in /"Luser, John Q./"~ ~%If you have a suffix, such as /"Jr./", put it at the end, after a second~ ~%comma: /"Luser, John Q., Jr./". Please capitalize your name as you wish~ ~%the machine to.") (def-item NICK-NAME 30. I$NICK "NICK" "Nickname" "~&Nickname. What you like to be called.") (def-item MIT-ADDRESS 80. I$MITA "MITAD" "MIT address" "~&MIT building # and room #, as in /"10-251/".~ ~%(Note: 545 Technology Sq. is building # NE43.)~ ~%If you do not frequent MIT, use your address at work.") (def-item MIT-PHONE 30. I$MITT "MITTE" "MIT phone #" "~&Your MIT phone number(s), as in /"3-1415/".") (def-item HOME-ADDRESS 80. I$HOMA "HOMAD" "Home address" "~&Your home address.") (def-item HOME-PHONE 30. I$HOMT "HOMTE" "Home phone #" "~&Your home phone number.") (def-item NETWORK-ADDRESS 40. I$NETA "NETAD" "Network address" "~&This is where you specify your /"home address/" on the network;~ ~&where you receive your computer mail.~ ~&~%If you want to receive your computer mail on this system, just ~&type a Return. Otherwise, enter a network host name, or~ ~&username@hostname. Your computer mail will be forwarded there.~ ~&~%Do not put a U.S. mail address here, since the network mailing~ ~&programs will not understand what you mean. ~&~%Note : o Do not give just as username with no hostname.~ ~& o Do not specify a TAC as the hostname, since TACs are~ ~& unable able to receive mail for you.~ ~&~%Just type a Return if you don't know what to do with this field.") (def-item SUPERVISOR 30. I$SUPR "SUPER" "Supervisor" "~&Your supervisor//advisor's name(s).") (def-item PROJECT 40. I$PROJ "PROJE" "Project" "~&The name of your project.~ ~%This will fill in /"I am hacking _____ for /".") (def-item FILE-DIRECTORIES 40. I$DIR "FILDI" "File directory name(s)" "~&The name of your home directory.~ ~%Type the names of the directories you want as your HSNAME on the~ ~%various ITS machines. You should not mention machines on which~ ~%you have a directory of your own.~ ~&~%A directory with no machine name applies to all machines that~ ~%such a directory exists on.~ ~&~%Example: /"SIPB@MC,KBS/" specifies SIPB on the MC machine, and KBS~ ~%on all other machines with a KBS directory.~ ~&~%Note: Do not specify USERSn or GUESTn directories here.~ ~& These are chosen for you by the system, and are~ ~& subject to change, and ARE NOT CONSTANT FROM ONE~ ~& MACHINE TO ANOTHER.") (def-item GROUP-AFFILIATION 1. I$GRP "GROUP" "Group" "~&Group affiliation. Enter one letter only.~ ~%Choose the one that fits best, from the following list:~ ~%A - Artificial Intelligence Lab person.~ ~%B - Educational Computing Group person.~ ~%C - Theory Group person.~ ~%L - Laboratory for Computer Science person.~ ~%P - Plasma Fusion Center person.~ ~%S - MIT guest - student//staff//faculty not in one of the other groups.~ ~%T - Guest (tourist).~ ~%V - NIL Group.+ ~%Z - Clinical Decision Making person.~ ~%+ - Official maintainter//Liaison on some MIT computer system.~ ~%$ - Official maintainter//Liaison on some ARPANET computer system.~ ~%@ - This is an alias for someone known under another name.~ ~%O - Other. This designates a program, not a person.") (def-item RELATION-TO-GROUP 1. I$REL "RELAT" "Relation" "~&Your Relation to your group. Enter one letter only.~ ~%Choose the one that fits, best from the following list:~ ~%A - Administrative F - Faculty~ ~%G - Graduate student P - Publications//Editing~ ~%R - Research associate S - DSR (sponsored research) staff~ ~%U - Undergraduate student ~ ~%X - Ex-user (former MIT staff//faculty)~ ~% N - None~%") (def-item BIRTHDAY 20. I$BRTH "BIRTH" "Birthday" "~&Birthday. The format should be as in /"January 1/".~ ~%(ie, no year or European-style formats).") (def-item REMARKS 630. I$REM "REMAR" "Remarks" "~&Remarks. Put anything in here you want to add.~ ~%It will be printed whenever someone looks you up.") ;(def-item MACHINES-KNOWN-ON 80. I$MACH "MACHI" "Machines known on" ; "~&This is not used at the present time.~ ; ~&Your INQUIR entry is automatically known on all ITS machines.") ; ;(def-item LOCAL-FIELDS 30. I$LOCL "LOCAL" "Local fields" ; "~&This is not used at the present time. It goes with Machines Known On") (def-item ALTERATION-DATE 0. I$ALTR "ALTER" "Last alteration" "~&This says who last altered your INQUIR entry, and when they did so.~ ~&(This is filled in automatically for you.") ;;;; Groups and Relations ;;; (We don't use this yet; need it for INQREP stuff later.) ;;; Associate the group letters with their documentation. (defvar *inquir-groups-alist* (list (cons #/A "Artificial Intelligence Lab person") (cons #/B "Educational Computing Group person") (cons #/C "Theory Group person") (cons #/L "Laboratory for Computer Science person") (cons #/P "Plasma Fusion Center person") (cons #/S "MIT guest - student//staff//faculty not in one of the other groups") (cons #/T "Guest (tourist)") (cons #/V "NIL Group") (cons #/X "Ex-user (former MIT staff//faculty") (cons #/Z "Clinical Decision Making person") (cons #/+ "Official Maintainter//Liaison on some MIT computer system") (cons #/$ "Official Maintainter//Liaison on some ARPANET computer system") (cons #/@ "This is an alias for someone known under another name") (cons #/O "Other. This designates a program, not a person"))) ;;; Associates the relation letters with their documentation. (defvar *inquir-relats-alist* (list (cons #/A "Administrative") (cons #/F "Faculty") (cons #/G "Graduate student") (cons #/P "Publications//Editing") (cons #/R "Research associate") (cons #/S "DSR (sponsored research) staff") (cons #/U "Undergraduate student ") (cons #/N "None"))) ;;;; I/O Support functions. ;;; Feep the bell. (defun beep () (tyo #^G)) ;Ding. ;;; Function to strip away those nasty chars. (defun trim-bad-chars (char-list) (delete '#.(ascii #\Null) char-list)) ;;; "A" or "An"? from KMP's ANIMAL technology. (defun a-or-an (symbol) (let ((ch (char-upcase (getcharn symbol 1))) (len (flatc symbol))) (cond ((= len 1) (cond ((member ch '(#/A #/E #/F #/H #/I #/L #/M #/N #/O #/R #/S #/X)) 'an) (t 'a))) (t (cond ((member (char-upcase ch) '(#/A #/E #/I #/O #/U)) 'an) (t 'a)))))) ;;; When we pass reading functions around, there callers ;;; sometimes need to know what the best break character ;;; is, for documentation purposes. (defvar *terminator* #\return "The terminator we're looking for") (defmacro with-terminator (terminator &body forms) `(let ((*terminator* ,terminator)) ,@forms)) ;;; REASONABLE-DISPLAY-TERMINAL? returns T if the TTY is something ;;; I could stand with e.g. automatic LISTME turned on. (defun reasonable-display-terminal? (&optional (tty (status ttyofa))) (let ((tty-flags (cdr (status filemode tty))) (tty-speed (status ospeed tty))) (if (and (memq 'rubout tty-flags) (memq 'cursorpos tty-flags) (or (zerop tty-speed) (>= tty-speed 1200.)) (> (car (status ttysize tty)) (plus 2. (length *inquir-items*)))) t NIL))) ;;; DISPLAY-ITEM prints an item as for a LISTME display. (defvar *display-start-col* 25.) (defun display-item (stream item &optional (start-col *display-start-col*) (max-width #.(cdr (status ttysize)))) (format stream "~&~A:~VT" (doc item) start-col) ;Print item name. (let* ((val (inquir-value item)) (chars (if val (exploden val)))) ;Get value. (do ((c chars (cdr c)) (new-line-flag nil) (pos-avail max-width)) ((null c)) (let ((c (car c))) (cond (new-line-flag (cond ((= c #\LF) (tyo c stream) (format stream "~VT" start-col) (setq pos-avail max-width)) (t (format stream "~VT" start-col) (setq pos-avail max-width) (tyo c stream)))) (t (tyo c stream) (setq pos-avail (1- pos-avail)))) (setq new-line-flag (= c #\CR)) (when (zerop pos-avail) (terpri stream) (format stream "~VT" start-col) (setq pos-avail max-width)))))) ;Restart count. ;;; Print the name and value of an item as INQUPD likes to see it. (defun print-inqupd-item (stream item) (format stream "~&~A:~C" (inqupd-name item) #\tab) ;Item name. (let* ((val (inquir-value item)) (chars (if val (exploden val))) (new-line-flag nil)) (dolist (c chars) (cond (new-line-flag (cond ((= c #\LF) (tyo c stream) (tyo #\tab stream)) (t (tyo #\tab stream) (tyo c stream)))) (t (tyo c stream))) (setq new-line-flag (= c #\CR))))) ;;;; Prettier MORE processing. ;;; Stolen from TNP;FORT. ;;; CATCH-**MORE** ;;; Catch a throw to flush output after a **MORE**. (defvar *can-flush-more* nil) (defvar *in-more-break* nil) (defmacro catch-**more** (&body body) `(*catch '*can-flush-more* (let ((*can-flush-more* t)) ,@body))) ;;; DONT-CATCH-**MORE** (defmacro dont-catch-**more** (&body body) `(let ((*can-flush-more* nil)) ,@body)) (defun **more** (stream) (nointerrupt nil) (do-with-tty-off (let ((*in-more-break* t)) (cond (*can-flush-more* (maybe-**more** stream (status ttycons stream))) (t (surely-**more** stream (status ttycons stream))))))) (defun maybe-**more** (outstream instream) (let ((where (cursorpos outstream))) (cursorpos 'l outstream) (format outstream "--More?--") (do ((c (tyi instream) (tyi instream)) (flag t)) (nil) (cond ((= c #\rubout) (cursorpos (car where) (cdr where) outstream) (cursorpos 'l outstream) (format outstream "--Output flushed--~%") (*throw '*can-flush-more* t)) ((= c #\space) (cursorpos (car where) (cdr where) outstream) (cursorpos 'l outstream) (format outstream "--Continuing--~%") (return t)) (flag (setq flag nil) (format outstream "(Type SPACE to continue or RUBOUT to flush output)")))))) (defun surely-**more** (outstream instream) (let ((where (cursorpos outstream))) (cursorpos 'l outstream) (format outstream "--Pause--") (do ((c (tyi instream) (tyi instream)) (flag t)) (nil) (cond ((= c #\space) (cursorpos (car where) (cdr where) outstream) (cursorpos 'l outstream) (format outstream "--Continuing--~%") (return t)) (flag (setq flag nil) (format outstream "(Type SPACE to continue)")))))) ;;;; Hacking items ;;; These variables control the mode of interaction: (defvar *brief-prompt* "What next? ") (defvar *windy-prompt* "What next? (Type ? for assistance): ") (defvar *prompt* *brief-prompt* "The top-level command prompt.") (defvar *windy-mode* nil "T for maximum verbosity") (defvar *display-mode* nil "T to do automatic LISTME after each change.") (defvar *verify-mode* nil "T to require verification on input") (defvar *modified* nil "T if the current entry has been changed.") (defvar *inhibit-listme?* nil "T to inhibit LISTME in CHANGE-ITEM.") ;;; CHANGE-ITEM is called by commands which change items. ;;; It provides a uniform kind of prompt, and hacks verification. ;;; You pass it a function to read the item and the argumentsts for same. (defun change-item (item-name read-fn &rest read-args) (lexpr-funcall #'change-item1 item-name read-fn read-fn read-args) (if (and *display-mode* (not *inhibit-listme?*)) (listme))) (defun change-item1 (item-name read-fn verify-fn &rest read-args) (let ((val (inquir-value item-name))) (if val ;If something already filled in (format t "~&~A~%~A" (doc item-name) val)) ; print it out. (terpri t) (format t (if *windy-mode* (windy-doc item-name) (doc item-name))) (if *terminator* (format t "~&End your input with a ~@:C.~%" *terminator*) (format t "~&")) (setf (inquir-value item-name) (lexpr-funcall read-fn read-args)) (setq *modified* t) (if *verify-mode* ; Now make sure he really meant it. (do ((newnewval (lexpr-funcall #'verify-item item-name verify-fn read-args) (lexpr-funcall #'verify-item item-name verify-fn read-args))) ((not newnewval)) )))) ;;; Returns NIL when the user is satisfied with the changes. (defun verify-item (item-name read-fn &rest read-args &aux newval) (format t "~& Is this right for ~A?" (doc item-name)) (format t "~& Type what it should look like, or ~@:C if this looks good.~ ~&~A~%" (or *terminator* #\Return) (inquir-value item-name)) (setq newval (lexpr-funcall read-fn read-args)) ;Get a replacement value. (cond ((not (string-equal newval "")) ;Store it. (setf (inquir-value item-name) newval) newval))) ;Return it, or NIL if there was no replacement. ;;; TTY item reader, calls READER. ;;; Returns a string. (defun inputter (&optional (prompt "->") (max #.(* 24. 80.)) multi? upcase?) (*catch 'too-long (let ((chars (nreverse (mapcar #'(lambda (x) (setq x (getcharn x 1)) (if upcase? (setq x (char-upcase x))) x) (reader prompt (if multi? '(#^C) '(#\CR #\LF #^C))))))) (if (and multi? (> (length chars) 1) (= (car chars) #\CR)) (pop chars)) ;Flush trailing CR. (when (> (length chars) max) (format t "~&That's too long for this item.") (dolist (c chars) ;Let him try again. (untyi c tyi)) (*throw 'too-long t)) (string (implode (nreverse (trim-bad-chars chars))))))) ;;; For reading in items: ;;; PROMPT-TYI for single chars. ;;; PROMPT-READLINE for single lines. ;;; PROMPT-INPUT for multiple lines. ;;; INPUT-SIXBIT for six uppercase chars. (defun prompt-readline (&optional (prompt "->") max) (inputter prompt max nil nil)) (defun prompt-tyi (&optional (prompt "->") (upcase? t) &aux c) (format t "~A" prompt) (setq c (tyi)) (if (or (equal c #\CR) (equal c #\LF)) "" (if upcase? (ascii (char-upcase c)) (ascii c)))) (defun input-sixbit (&optional (prompt "->")) (inputter prompt 6. nil t)) (defun prompt-input (&optional (prompt "->") (max)) (inputter prompt max t nil)) ;;; INPUT-NETADDR is for typing in network addresses. (defun input-netaddr (&rest read-args) (let ((netaddr (lexpr-funcall #'inputter read-args))) (if (string-equal netaddr "") (string (status site)) netaddr))) ;;; Fancy completing command reader, calls *COMPLETING-READ1. (defun get-input-line (prompt options &optional (over-rubout-allowed? t)) (cond ((zerop (charpos tyo)) (format t "~&~A" prompt))) (prog1 (do () (nil) (let ((command (*completing-read1 prompt options '() tyi tyo t t '(#\ALT) '(#\RETURN #\SPACE) t over-rubout-allowed?))) (cond ((memq command '(over-rubout line-rubout)) (abort-command)) ((atom command) ;line-rubout, word-rubout, etc. (format t "~&~A" prompt)) (t (return (cadr command)))))) (cond ((and (plusp (listen tyi)) (= (tyipeek nil tyi) #\LF)) (tyo (tyi tyi) tyo))))) ;;;; LSR1 and LOGOUT TIMES database hacking. ;;; ;;; LSRRTN primitives: ;;; ;;; (LSRMAP ) ==> file object. ;;; (LSRDTA ) ==> core address of first entry or -1 ;;; (LSRNXT ) ==> core address of next entry or -1 ;;; (LSRUNM ) ==> core address or -1 ;;; (LSRITM ) ==> item symbol frob ;;; ;;; NETRTN primitives: ;;; ;;; (MAP-HOSTS3) ==> gets the network database in core. ;;; (OWN-HOST network-number) ==> host. ;;; (HOST-EQUAL host1 host2) ==> T iff they refer to same host. ;;; (LOOKUP-HOST host-name) ==> host-number. ;;; (HOST-SIXBIT-NAME host) ==> sixbit host name. (defvar *lsr1* nil) ;LSR1 file object. ;(defvar *logout-times* nil) ;LGOTIM file object. ;;; Map databases. (defun map-inquir () (setq *lsr1* (lsrmap 20.))) ;(defun map-logout-times () ; (setq *logout-times* (map-lgotim))) ;;; GET-ENTRY reads the data for the current INQUIR entry from the ;;; LSR1 database. The LSR1 database must already be mapped in. ;;; If the named user does not have an entry, GET-ENTRY returns NIL. (defun get-entry (uname) (without-interrupts ;No interruptions, please. ;; Remember who the current entry belongs to. ;; Try looking up in the LSR1 database. (let ((entry (lsrunm *lsr1* uname)) (val)) (cond ((and entry (not (= entry -1))) (dolist (item *inquir-items*) ;Fill in current entry from LSR1. (setq val (lsritm (i$number item) entry)) (setf (inquir-value item) (string val))) t) (t ;If unknown, just set Uname. (setf (inquir-value 'user-name) (string (string-upcase uname))) nil))))) (defvar *default-local-items-string* "FILE-DIRECTORIES GROUP-AFFILIATION RELATION-TO-GROUP") ;;; LAST-ALTERATION returns the current timestamp string for an ALTER item. (defun last-alteration () (lexpr-funcall #'format nil "~A ~2,'0D~2,'0D~2,'0D-~2,'0D~2,'0D~2,'0D" (status uname) (append (status date) (status daytime)))) ;;; UPDATE-INQUIR updates the LSR1 file from the current Inquir entry. ;;; (Actually, it mails off a request for INQUPD to do so.) ;;; This is the mail routine usually called by DONE. (defvar *inqupd-request-file* "DSK:.MAIL.;MAIL >") ;;; We have to use this until we get HOSTS3 going in Lisp. ;;; Would you believe...until we get domains going in Lisp! (defun old-style-update-inquir () (setf (inquir-value 'machines-known-on) (string-append *suname* "@AI @MC @ML @MD @SV @SX")) (if (or (null (inquir-value 'authorization)) (string-equal (inquir-value 'authorization) "")) (setf (inquir-value 'authorization) "*")) (setf (inquir-value 'local-fields) "FILDI GROUP RELAT") (setf (inquir-value 'alteration-date) (last-alteration)) (with-open-file (s *inqupd-request-file* '(out)) (format s "~&FROM-PROGRAM:~A~ ~&AUTHOR:~A~ ~&RCPT:(UPDATE-ITS-INQUIR)" (status jname) (status uname)) (format s "~&TEXT;-1~ ~&BEGIN:~%~ ~&SUNAME:~C~A" #\tab *suname*) (dolist (item *inquir-items*) (print-inqupd-item s item)) (format s "~&END:~%"))) ;;;; UPDATE-INQUIR is called by the DONE command to update all the ;;;; databases mentioned. ; ;(defun update-inquir () ; ;; Set up the ALTER item. ; (setf (inquir-value 'alteration-date) (last-alteration)) ; (let ((macs (list-from-string (inquir-value 'machines-known-on))) ; (updated-hosts nil)) ; (dolist (mac macs) ; (let* ((machine (substring mac (1+ (string-search "@" mac)))) ; (suname (substring mac 0 (string-search "@" mac))) ; (host (lookup-host machine))) ; ; (when (not (mem #'host-equal host updated-hosts)) ; (update-one-machine suname machine) ;; (push host updated-hosts)))))) ;;;;; *** Need to make sure local host is updated with all the items! ; ; ; ; ;;;; UPDATE-ONE-MACHINE sends off an update for a single database. ;;;; It can optionally ignore the LOCAL-FIELDS item. ; ;(defun update-one-machine (suname machi &optional (use-local-fields? t)) ; (with-open-file (s *inqupd-request-file* '(out)) ; (format s "~&FROM-PROGRAM:~A~%~ ; ~&AUTHOR:~A~%~ ; ~&RCPT:(UPDATE-INQUIR ~A)~%~ ; ~&TEXT;-1~ ; ~&BEGIN:~%~ ; ~&SUNAME:~C~A" ; (status jname) ; (status uname) ; machi ; #\tab suname) ; (let ((locals (list-from-string (inquir-value 'local-items)))) ; (dolist (item *inquir-items*) ; (if (and use-local-fields? ; (not (member item locals))) ; (print-inqupd-item s item))) ; (format s "~&END:~%")))) ;;;; Commands. ;;; Amazing hairy macro defining macro frobozz. (defmacro def-command-type (name) (let ((doc (symbolconc name '-documentation)) (var (symbolconc '* name 's*)) (def (symbolconc 'define- name)) (fn (symbolconc name '-fn))) `(progn 'compile (defmacro ,fn (x) `(get ,x ',',name)) (defmacro ,doc (x) `(get ,x ',',doc)) (defvar ,var '()) (defmacro ,def (name doc &body body) `(progn 'compile (push ',name ,',var) (defun (,name ,',name) () ,@body) (setf (,',doc ',name) ',doc) ',name)) (,def HELP "Gives help on a given command type." (format t "~&~A" (,doc (get-input-line ',(format nil "Help with ~A: " name) ,var))) (terminate-command)) ',name))) (defun not-implemented () (format t "~&I don't know how to do that to ~A ~A, yet!" (string-downcase (a-or-an *suname*)) *suname*)) ;;;; Primary commands. (def-command-type command) (define-command QUIT "Exits the program without updating any entries." (cond (*modified* (if (yes-or-no-p "~&Cancel all these changes? ") (flush-inquir t) (format t "~&Use the DONE command when you are happy with ~ your changes."))) (t (flush-inquir t)))) (define-command DONE "Exits the program and starts the update." (cond (*modified* (old-style-update-inquir) (format t "~&Your changes have been sent for processing and~ ~&should be completed in a little while.")) (t (format t "~&(No changes made.)"))) (format t "~&Thank you.~%") (flush-inquir t)) ;;; Commands which affect the mode of interaction. (define-command WINDY "Make INQUIR be verbose." (setq *windy-mode* t) (setq *prompt* *windy-prompt*) (format t "~&Maximum verbosity.")) (define-command BRIEF "Make INQUIR be less verbose." (setq *windy-mode* nil) (setq *prompt* *brief-prompt*)) (define-command VERIFY "Make INQUIR verify all your changes." (setq *verify-mode* t)) (define-command NOVERIFY "Make INQUIR stop asking you to verify all your changes." (setq *verify-mode* nil)) (define-command LISTME "Show what your entry looks like." (listme)) ;;; Routine to list our entry. ;;; Called automatically from CHANGE-ITEM when in display mode. (defun listme () (cursorpos 'C) (dolist (item *inquir-items*) (display-item t item)) (terpri) (terpri)) (define-command DESCRIBE "Tell about a particular INQUIR item." (let ((item (get-input-line "Describe item: " *inquir-items*))) (format t (string-append "~&~%" (doc item) ".~&" (or (windy-doc item) "") "~2&Current value is:~&~A~2%") (inquir-value item)))) (define-command REVERT "Cancel all changes; read your old info back in from the database." (if (yes-or-no-p t "~%Cancel all these changes? ") (cond ((get-entry *suname*) (format t "~&OK.") (setq *modified* nil) (listme)) (t (abort-command "You have no previous info on file."))) (abort-command))) ;(define-command NOTME "Update someone else's entry." ; (not-implemented)) ;(define-command OTHER "Prefix to other, less commonly used commands." ; (not-implemented)) (define-command ALL "Change all of your items." (change-all-items nil)) ;;; CHANGE-ALL-ITEMS is for changing all the fields in an entry. ;;; FIRST-TIME? is whether we should ask only for the items we ;;; expect novices running INQUIR for the first time to fill in. (defun change-all-items (first-time?) (let ((*inhibit-listme?* t)) (dolist (cmd '(NAME NICK-NAME MIT-ADDRESS MIT-PHONE HOME-ADDRESS HOME-PHONE NETWORK-ADDRESS SUPERVISOR PROJECT GROUP RELATION BIRTHDAY REMARKS)) (funcall (command-fn cmd))) (if (not first-time?) (dolist (cmd '(AUTHORIZATION FILE-DIRECTORIES)) (funcall (command-fn cmd)))))) ;(define-command WHOIS "Display your entry like WHOIS would show it." ; (let ((uname (inquir-value 'uname))) ; (format t "~2&~A ~C~C ~A Last logout ~A" ; uname ; (inquir-value 'group-affiliation) ; (inquir-value 'relation-to-group) ; (first-name-first (inquir-value 'name)) ; (implode (last-logout *logout-times* uname))) ; (format t " (~A) [~A] hacking ~A for ~A~ ; ~% Birthday ~A; ~A; ~A~ ; ~% ~A; ~A~%" ; (inquir-item 'nick-name) ; (inquir-item 'network-address) ; (inquir-item 'project) ; (inquir-item 'supervisor) ; (inquir-item 'birthday) ; (first-line-of (inquir-item 'mit-address)) ; (inquir-item 'mit-phone) ; (first-line-of (inquir-item 'home-address)) ; (inquir-item 'home-phone)))) ;;;; Commands which change items ;;; I had considered making a form which would automatically ;;; write these commands, but I don't think it will save ;;; enough typing to bother with. Something like: ;;; ;;; (defmacro def-item-command (item cmd-doc input-fn) ;;; `(progn 'compile ;;; (setf (initial-asked? ',item) ,initial-asked?) ;;; (define-command ,item ,cmd-doc ;;; (change-item ',item ,input-fn "->" (max-length ',item))))) ;;; (define-command USER-NAME "Changes your username." (change-item 'user-name #'inputter "->" 6. nil t)) (define-command NAME "Changes your full personal name." (change-item 'name #'inputter "->" (max-length 'name))) (define-command NICK-NAME "Changes your nickname." (change-item 'nick-name #'inputter "->" (max-length 'nick-name))) (define-command GROUP "Changes your group affiliation." (with-terminator nil (change-item 'group-affiliation #'prompt-tyi "->" t))) (define-command RELATION "Changes your group relation." (with-terminator nil (change-item 'relation-to-group #'prompt-tyi "->" t))) (define-command AUTHORIZATION "Say whether you are a good-guy." (change-item 'authorization #'inputter "->" (max-length 'authorization))) (define-command MIT-ADDRESS "Change your MIT (work) address." (with-terminator #^C (change-item 'mit-address #'inputter "-> " (max-length 'mit-address) t nil))) (define-command HOME-ADDRESS "Change your home address." (with-terminator #^C (change-item 'home-address #'inputter "-> " (max-length 'home-address) t nil))) (define-command MIT-PHONE "Change your MIT (office) phone." (change-item 'mit-phone #'inputter "->" (max-length 'mit-phone))) (define-command HOME-PHONE "Change your home phone." (change-item 'home-phone #'inputter "->" (max-length 'home-phone))) (define-command SUPERVISOR "Change your supervisor." (change-item 'supervisor #'inputter "->" (max-length 'supervisor))) (define-command PROJECT "Change your project." (change-item 'project #'inputter "->" (max-length 'project))) (define-command FILE-DIRECTORIES "Change your home file directories." (change-item 'file-directories #'inputter "->" (max-length 'file-directories))) (define-command BIRTHDAY "Change your birthday." (change-item 'birthday #'inputter "->" (max-length 'birthday))) (define-command REMARKS "Change your remarks." (with-terminator #^C (change-item 'remarks #'inputter "->" (max-length 'remarks) t nil))) (define-command NETWORK-ADDRESS "Change your electronic mail address." (change-item1 'network-address #'input-netaddr #'inputter "->" (max-length 'network-address) nil nil) (if (and *display-mode* (not *inhibit-listme?*)) (listme))) ;(define-command MACHINES "Change which database your entry is updated on." ; (not-implemented)) ;(define-command LOCAL-FIELDS "Change which fields are not propogated to foreign INQUIR databases." ; (not-implemented)) ;;;; Other commands. (def-command-type extended-command) (define-command EXTENDED "Preface to less frequently used commands." (funcall (extended-command-fn (get-input-line "What else? " *extended-commands*)))) (define-command DISPLAY-MODE "Manually force display terminal mode on." (setq *display-mode* t)) (define-command NODISPLAY-MODE "Manually force display terminal mode off." (setq *display-mode* nil)) (define-extended-command DELETE "Delete the entry you are editing from the database. INQUIR will forget all about this person." (terpri t) (cond ((yes-or-no-p t "Do you really want to flush ~:[your~*~;~A's~] INQUIR entry? " (not (eq (status xuname) *suname*)) *suname*) (setf (inquir-value 'user-name) "") (setq *modified* t) (format t "~&OK.") (if *display-mode* (listme))) (t (abort-command)))) ;(define-extended-command LIST-USERS ; "List users." ; ;; Massive non-destruction! ; (not-implemented)) ;;; BREAK loop is for hackers only. ;;; Maybe should be binding ^X and ^G to normal frobs, and ^B to Abort Command. (define-extended-command BREAK "Enter a break loop for debugging!" (setq *can-flush-more* nil) (break "Type P to continue.") (setq *can-flush-more* t)) (define-extended-command IGNORE "Ignore you and get back to regular commands." (terminate-command)) ;;;; Reaping the INQUIR database. ;;; These should really be on a seperate INQREP comtab. (define-extended-command MASSIVE "Do massive updates to the database." ;; Massive destruction! user could supply predicate somehow. (not-implemented)) (define-extended-command NEXT-LUSER "Update the next luser's entry (used after a MASSIVE command.)" (not-implemented)) ;;;; Spiels. ;;; If we are started as inqchk, we give a little explanatory spiel ;;; before getting down to business. (defun inqchk-spiel () (format t "~2%The program /"INQUIR/" is being run for you so that you can tell us~ ~%who you are. When you are done running inquir you will be left in~ ~%DDT, the command interpreter for this computer system.")) ;;; Here is the long spiel explaining what inquir is, and giving general help. (defun explain-inquir-spiel () (format t "~&~%The purpose of this program is to maintain a list of users for this~ ~%computer. It will lead you by the hand; just provide the information~ ~%requested. If you know what you are doing and don't like being lead,~ ~%use the BRIEF command.~ ~2%Each user on ITS has an individual user-name, or /"UNAME/"; a word of~ ~%six characters or less, which you use to log in. INQUIR keeps its~ ~%records filed under people's UNAMEs.~ ~2%Your dossier consists of several items; each is one piece of ~ ~%information, such as your name, or your home address. ~%If you are completely unknown, INQUIR will ask you for all of the~ ~%items in order. If you are already known, you can type the name~ ~%of any items you wish to alter.~ ~2%You can leave any of the items blank, if you feel the questions are~ ~%too nosey. Most people fill in most of the items so that people in~ ~%the user community can find out about them.~ ~2%If you are on a display terminal, your entire dossier will be shown~ ~%frequently. On printing terminals, you must type the LISTME command~ ~%to see it.~ ~2%If you make a mistake typing, you can use the Rubout key to erase a~ ~%character. Typing ^U (holding the Control key down and striking /"U/")~ ~%will erase the line you are typing. To redisplay the line you are~ ~%typing, type ^R. To clear the screen and redisplay the line, type ^L.~ ~2%When INQUIR is ready for a command, it will prompt you.~ ~%You can type a question mark (?) to see what the possibilities are.~ ~%If you type a question mark in the middle of a command name, ~%INQUIR will show you what the possible completions are. ~%Typing a Space or an Escape will complete the command name for you.")) ;;;; Startup. ;;; TOPLEVEL is magically called when we start up. (defun toplevel () (initialize) ;Map in database, etc. (dont-catch-**more** (start-inquir)) ;Prologue. (do ((first-time-thru t nil)) (nil) ;This is the command infinite-loop. (*catch 'command-abort (catch-**more** (bind-ttyint ((#^G #'abort-command-handler) (#^S #'flush-command-handler) (#^B NIL) (#^C NIL) (#^D NIL) (#^R NIL) (#^T NIL) (#^V NIL) (#^W NIL) (#^X NIL)) (when (and first-time-thru *display-mode*) (listme)) (format t "~&") (when (not (errset (funcall (command-fn (get-input-line *prompt* *commands* nil))) t)) (bug nil))))))) (defun initialize () (without-interrupts (setq *inquir-items* (nreverse *inquir-items*)) (setq *commands* (nreverse *commands*)) (map-inquir) ;Map in the LSR1 database. #| (map-logout-times) |# ;Map in the LOGOUT TIMES database. (setq *display-mode* (reasonable-display-terminal?)) (endpagefn t #'**more**))) ;Turn on **MORE** handler. ;;; START-INQUIR makes sure the luser knows who is being hacked, ;;; figures out how verbose to be, and makes new users fill out ;;; everything before returning to the main program. (defun start-inquir () (unless (errset (*catch 'command-abort (catch-**more** (bind-ttyint ((#^G #'flush-inquir-handler) (#^S #'flush-inquir-handler) (#^B NIL) (#^C NIL) (#^D NIL) (#^R NIL) (#^T NIL) (#^V NIL) (#^W NIL) (#^X NIL)) (let ((inqchk? (eq (status jname) 'INQCHK)) (jcl (jcl-as-string))) (setq *suname* (or jcl (string-upcase (status xuname)))) (cond (inqchk? ;When started by DDT init file, (when (get-entry *suname*) ; if luser already exists (flush-inquir t)) ; then punt right now. (unless (yes-or-no-p t "~%You have logged in as ~A, which is a new UNAME.~ ~&Is this what you you intended? " *suname*) (format t "~&Please type ^Z and try again.") (valret "0U")) (inqchk-spiel) (funcall (command-fn 'WINDY)) (funcall (command-fn 'VERIFY)) (enter-new-user)) (t ;See who the luser is hacking. (format t "~&~@[~A ~]INQUIR.~D" (get 'inquir 'release-status) (get 'inquir 'version)) (when (null jcl) (when (null (get-entry *suname*)) (format t "~%~A is not known." *suname*)) (unless (y-or-n-p t "~&Is ~A your user name? " *suname*) (format t "~&Well, that's who you are logged in as.~ ~&If you want to modify someone else,~ ~&invoke INQUIR with their UNAME as JCL.") (flush-inquir t))) (cond ((null (get-entry *suname*)) (unless jcl (explain-inquir-spiel) (funcall (command-fn 'WINDY)) (funcall (command-fn 'VERIFY))) (enter-new-user)) (t ;; Luser exists, so just set the verbosity level. (funcall (command-fn 'NOVERIFY)) (funcall (command-fn (if jcl 'BRIEF 'WINDY))))))))))) t) (bug t))) (defun enter-new-user () (*catch 'command-abort (bind-ttyint ((#^G #'abort-command-handler) (#^S #'flush-command-handler) (#^B NIL) (#^C NIL) (#^D NIL) (#^R NIL) (#^T NIL) (#^V NIL) (#^W NIL) (#^X NIL)) (terpri t) (change-all-items t)))) ;Then put luser to work. (defun jcl-as-string (&aux jcl) "JCL-AS-STRING finds out if there is JCL, and returns it or NIL. Flushes spaces and JCL-terminators." (setq jcl (status jcl)) (when jcl (let ((jcl (delete '#.(ascii #\return) (delete '#.(ascii #\space) (delete '#.(ascii #^C) (delete '#.(ascii #^_) jcl)))))) (if jcl ;Prevent "". (string-upcase (substring (implode jcl) 0 6)))))) ;;; TERMINATE-COMMAND, gets us back to toplevel. ;;; ;;; ABORT-COMMAND prints an error message for the user and aborts ;;; the current command. ;;; ;;; FLUSH-INQUIR is the way out of the program, rather than just QUIT. ;;; We offer the user the chance to flush INQUIR (or optionally don't bother.) (defun terminate-command (&rest ignore) (setq *can-flush-more* t) (*throw 'command-abort t)) (defun abort-command (&optional msg) (format t "~&~:[Command aborted.~;~A~]" msg msg) (terminate-command)) (defun flush-inquir-handler (stream nil) (clear-input stream) (flush-inquir)) (defun flush-inquir (&optional without-asking) (if (or without-asking (yes-or-no-p t "~&~%Do you really want to flush this program now? ")) (quit))) ;;; Bug reporting (defun bug (&optional awful? &rest ignore) (format t "~&~2%A bug in INQUIR has been encountered.~ ~%This should never happen.~ ~%Please send a message explaining the circumstances leading to this ~ ~%to Bug-INQUIR@MIT-MC. Thank you. ~%") (valret (if awful? ":PDUMP CRASH;INQUIR > :BUG INQUIR An INQUIR crash file was just dumped.  :KILL " ":PDUMP CRASH;INQUIR > :BUG INQUIR An INQUIR crash file was just dumped.  :CONTINUE ")) (terpri t)) ;;; Interrupt handlers. ;;; ABORT-COMMAND-HANDLER and FLUSH-COMMAND-HANDLER abort commands. (defun abort-command-handler (&rest ignore) (clear-input t) (abort-command)) (defun flush-command-handler (&rest ignore) (clear-input t) (format t "~&Flushed.~%") (terminate-command)) (defun flush-fallthru-handler (&rest ignore) (clear-input t) (format t "~&Flushed.~%")) ;;;; Utility Functions ;;; Searching the INQUIR database. (defun find (item-no target-string fn &optional (stream t) (display item-no)) (do ((entry (lsrdta *lsr1*) (lsrnxt *lsr1* entry)) (hits 0.) (count 0. (1+ count))) ((= -1 entry) (format stream "~&~%~D//~D entries." hits count)) (when (string-search target-string (string (lsritm item-no entry))) (funcall fn entry display stream) (setq hits (1+ hits))))) (defun print-entry (entry item-no &optional (stream t)) (format stream "~%~A~10T~A~&~A~%" (lsritm i$unam entry) (lsritm i$name entry) (lsritm item-no entry))) ;;; Local Modes: ;;; Lisp with-open-file Indent:1 ;;; Lisp with-terminator Indent:1 ;;; Lisp bind-ttyint Indent:1 ;;; Compile Command: :qc inquir;inquir >(t):inquir;inqdmp ;;; End: