Chicken Library: out

Format control strings can be quite ugly, and some common formatting tasks are ugly out of proportion to the complexity of what is being asked. So I have written out. It is inspired somewhat by the YTools out macro for Common Lisp, but right now owes more to C++.

I was inspiried to search for something nicer when I produced the following:

(define (seconds->sql-timestamp s)
  (match-let ((#(seconds minutes hours mday month year wday yday dst timezone)
                (seconds->local-time s)))
    (format "~d-~2,'0,d-~2,'0,d ~2,'0,d:~2,'0,d:~2,'0,d"
             (+ year 1900) (+ month 1) mday hours minutes seconds)))

Isn't that format control string lovely? I just wanted "2005-03-10 12:33:05".

This little out makes simple things simple. If you need to do anything too tricky, you'll need to revert to format. I use format quite a bit in out itself.

Here is source (it is not currently set up to be compiled as a module). This is early code. Use with caution.

Manipulators

In the simplest case out is just like the Chicken print. It will take all the arguments and send them to the current output port:

#;5> (out 1 'wow 2 '(a b c) #\newline)
1wow2(a b c)
#;6> 

Notice that there are no spaces between items, nor is a newline produced by default.

out's more sophisticated operations are handled by keywords which I call "maninpulators" due to the C++ ancestry of some of the design. Some of the maninpulators take an argument, which will be the item after them, some do not. The simplest is sugar: #:nl outputs a newline:

#;6> (out 1 'wow 2 '(a b c) #:nl)
1wow2(a b c)
#;7>  

You can change the base for exact numbers:

#;8> (out 33 " " #:bin 33 " " #:oct 33 " " #:hex 33 #:nl)
33 100001 41 21
#;9> (out #:bin 3 " " 5 " " 128 #:nl)
11 101 10000000
#;10> 

Notice that the base maninpulator stays in effect for the rest of the out stream.

There are parallel sets of manipulators for fixing the width and setting the pad character for columnar output. One set is for the numbers and one for everything else. To set the width for numbers use #:number-width (or #:nwidth - they operate the same).

#;11> (out #:number-width 5  33 #:nl 123 #:nl 3.7 #:nl)
00033
00123
003.7
#;12> 

Notice that the default fill character is zero. If you want spaces, use #:number-fillchar to change it:

#;12> (out #:number-width 5 #:number-fillchar " "
           33 #:nl 123 #:nl 3.7 #:nl)
   33
  123
  3.7
#;13> 

I now have the tools to produce a much nicer version of the SQL timestamp formatter I cited at the beginning:

(define (seconds->sql-timestamp s)
  (match-let ((#(seconds minutes hours mday month year wday yday dst timezone)
                (seconds->local-time s)))
    (out #:number-width 2 #:number-fillchar 0
         (+ year 1900) "-" (+ month 1) "-" mday " "
         hours ":" minutes ":" seconds #:nl)))

#;14> (seconds->sql-timestamp (file-modification-time "."))
2005-02-25 08:35:22
#;15>  

Of course, I'd want to wrap it in with-output-to-string most of the time.

To set the column width and fill character for other types, just use #:width and #:fillchar

#;15> (out #:width 10 #:fillchar #\x 
           "nifty" #:nl "spam" #:nl "eggs" #:nl)
xxxxxnifty
xxxxxxspam
xxxxxxeggs
#;16>  

All manipulators

The Future

Due to my limited ability to comprehend everything about the details of format columnar output is only right justified. When I can figure out how to make "~d" and "~f" left justify, I will update the library.

Output goes to the current output port. Perhaps it should go into a string?

Right now the trickiest operations have to do with exact and inexact numbers. Every other Scheme type is output via "~a". I may eventually redesign out so that you can install your own formatting handlers by type, and possibly allow for adding manipulators on the fly.





Scheme (Chicken)