Книга: Practical Common Lisp
Improving the User Interaction
Improving the User Interaction
While our add-record
function works fine for adding records, it's a bit Lispy for the casual user. And if they want to add a bunch of records, it's not very convenient. So you may want to write a function to prompt the user for information about a set of CDs. Right away you know you'll need some way to prompt the user for a piece of information and read it. So let's write that.
(defun prompt-read (prompt)
(format *query-io* "~a: " prompt)
(force-output *query-io*)
(read-line *query-io*))
You use your old friend FORMAT
to emit a prompt. Note that there's no ~%
in the format string, so the cursor will stay on the same line. The call to FORCE-OUTPUT
is necessary in some implementations to ensure that Lisp doesn't wait for a newline before it prints the prompt.
Then you can read a single line of text with the aptly named READ-LINE
function. The variable *query-io*
is a global variable (which you can tell because of the *
naming convention for global variables) that contains the input stream connected to the terminal. The return value of prompt-read
will be the value of the last form, the call to READ-LINE
, which returns the string it read (without the trailing newline.)
You can combine your existing make-cd
function with prompt-read
to build a function that makes a new CD record from data it gets by prompting for each value in turn.
(defun prompt-for-cd ()
(make-cd
(prompt-read "Title")
(prompt-read "Artist")
(prompt-read "Rating")
(prompt-read "Ripped [y/n]")))
That's almost right. Except prompt-read
returns a string, which, while fine for the Title and Artist fields, isn't so great for the Rating and Ripped fields, which should be a number and a boolean. Depending on how sophisticated a user interface you want, you can go to arbitrary lengths to validate the data the user enters. For now let's lean toward the quick and dirty: you can wrap the prompt-read
for the rating in a call to Lisp's PARSE-INTEGER
function, like this:
(parse-integer (prompt-read "Rating"))
Unfortunately, the default behavior of PARSE-INTEGER
is to signal an error if it can't parse an integer out of the string or if there's any non-numeric junk in the string. However, it takes an optional keyword argument :junk-allowed
, which tells it to relax a bit.
(parse-integer (prompt-read "Rating") :junk-allowed t)
But there's still one problem: if it can't find an integer amidst all the junk, PARSE-INTEGER
will return NIL
rather than a number. In keeping with the quick-and-dirty approach, you may just want to call that 0 and continue. Lisp's OR
macro is just the thing you need here. It's similar to the "short-circuiting" ||
in Perl, Python, Java, and C; it takes a series of expressions, evaluates them one at a time, and returns the first non-nil value (or NIL
if they're all NIL
). So you can use the following:
(or (parse-integer (prompt-read "Rating") :junk-allowed t) 0)
to get a default value of 0.
Fixing the code to prompt for Ripped is quite a bit simpler. You can just use the Common Lisp function Y-OR-N-P
.
(y-or-n-p "Ripped [y/n]: ")
In fact, this will be the most robust part of prompt-for-cd
, as Y-OR-N-P
will reprompt the user if they enter something that doesn't start with y, Y, n, or N.
Putting those pieces together you get a reasonably robust prompt-for-cd
function.
(defun prompt-for-cd ()
(make-cd
(prompt-read "Title")
(prompt-read "Artist")
(or (parse-integer (prompt-read "Rating") :junk-allowed t) 0)
(y-or-n-p "Ripped [y/n]: ")))
Finally, you can finish the "add a bunch of CDs" interface by wrapping prompt-for-cd
in a function that loops until the user is done. You can use the simple form of the LOOP
macro, which repeatedly executes a body of expressions until it's exited by a call to RETURN
. For example:
(defun add-cds ()
(loop (add-record (prompt-for-cd))
(if (not (y-or-n-p "Another? [y/n]: ")) (return))))
Now you can use add-cds
to add some more CDs to the database.
CL-USER> (add-cds)
Title: Rockin' the Suburbs
Artist: Ben Folds
Rating: 6
Ripped [y/n]: y
Another? [y/n]: y
Title: Give Us a Break
Artist: Limpopo
Rating: 10
Ripped [y/n]: y
Another? [y/n]: y
Title: Lyle Lovett
Artist: Lyle Lovett
Rating: 9
Ripped [y/n]: y
Another? [y/n]: n
NIL
- User Interaction
- 4.4.4 The Dispatcher
- About the author
- Chapter 7. The state machine
- Chapter 15. Graphical User Interfaces for Iptables
- Appendix E. Other resources and links
- Example NAT machine in theory
- The final stage of our NAT machine
- User-land setup
- Compiling the user-land applications
- User specified chains
- The conntrack entries