From: Tamas K Papp on 4 Dec 2009 05:34 Hi, I had to write a simple script, and decided to do it in CL. It is the language I know best, and I wanted to learn how to generate standalone executables in ECL (it was dead simple -- the whole thing is 38K). I would appreciate if people could comment on the code. It is nothing complicated and runs fine, I just want to learn the idiomatic way to use CL for scripting. For example, is line-by-line (see below) the recommended way to redirect a stream to a file? For the curious, the script is used to open the body of an e-mail message from mutt in the browser. I use this to translate the text of e-mails I get in foreign languages using firefox plugins. Easier than manual extraction of text & copy-paste. The mutt snippet looks like this: macro index,pager <F7> "\ <enter-command> set pipe_decode=yes<enter>\ <enter-command> set my_wait_key=\$wait_key wait_key=no<enter>\ <pipe-message>/home/tpapp/software/misc/savebody<enter>\ <enter-command> set wait_key=\$my_wait_key &my_wait_key<enter>\ <enter-command> set pipe_decode=no<enter>\ " "view message body in browser" Anyhow, here is the Lisp code: ;;;; This simple scripts saves standard input to a randomly generated ;;;; file (starting with *base-path*) and then calls a browser to ;;;; open this file (current setup obviously Ubuntu/Debian specific). ;; working with strings instead of pathnames (defparameter *base-path* "/tmp/savebody") ;; browser (defparameter *browser* "sensible-browser") (defun random-string (length &optional (allowed-chars "01234567890abcdef")) "Return a random string of given length, composed from allowed-chars." (let* ((n (length allowed-chars)) (string (make-string length))) (dotimes (i length) (setf (aref string i) (aref allowed-chars (random n)))) string)) (defun open-unique (&key (base-path *base-path*) (random-length 10) (max-tries 100)) "Return stream and path." (tagbody top (when (minusp max-tries) (error "could not open random file - this should not happen")) (let* ((random-path (concatenate 'string base-path (random-string random-length))) (stream (open random-path :direction :output :if-exists nil :if-does-not-exist :create))) (unless stream (decf max-tries) (go top)) (return-from open-unique (values stream random-path))))) (defun copy-stream (input output) "Copy input to output. No error handling." ;; is this the right way to do it? (tagbody top (multiple-value-bind (line missing-newline-p) (read-line input nil nil) (when line (if missing-newline-p (write-string line output) (write-line line output)) (go top))))) (multiple-value-bind (stream path) (open-unique) ;; open file and copy stdin (unwind-protect (copy-stream *standard-input* stream) (close stream)) ;; open browser (let ((args (list (concatenate 'string "file://" path)))) #+sbcl (sb-ext:run-program *browser* args :search t :wait nil) #+ecl (ext:run-program *browser* args)))
From: Pillsy on 4 Dec 2009 10:08 On Dec 4, 5:34 am, Tamas K Papp <tkp...(a)gmail.com> wrote: [...] > I would appreciate if people could comment on the code. It is nothing > complicated and runs fine, I just want to learn the idiomatic way to > use CL for scripting. You really like those TAGBODYs and GOs don't you? :) > For example, is line-by-line (see below) the recommended way to redirect > a stream to a file? That's how I've always done it. I never even realized that READ-LINE had that second return value; that almost makes up for the way it mixes optional and keyword arguments. [...] Cheers, Pillsy
From: fortunatus on 4 Dec 2009 10:20 You could replace the tagbody with simple LOOP and LOOP-FINSH, or you could use the complex LOOP keywords like DO and WHILE. But frankly I find your TAGBODY and GO to be quite clear as it is, even probably more clear than the complex loop keywords would be. Otherwise I think your code is great! By the way, would you mind posting what you did for your ECL exec? Thanks! (loop :do (multiple-value-bind ... ) :while line :do (if missing-new-line (...) (...))
From: fortunatus on 4 Dec 2009 10:23 On Dec 4, 10:20 am, fortunatus <daniel.elia...(a)excite.com> wrote: > You could replace the tagbody with simple LOOP and LOOP-FINSH, or you > could use the complex LOOP keywords like DO and WHILE. But frankly I > find your TAGBODY and GO to be quite clear as it is, even probably > more clear than the complex loop keywords would be. Otherwise I think > your code is great! > > By the way, would you mind posting what you did for your ECL exec? > Thanks! > > (loop > :do (multiple-value-bind ... ) > :while line > :do (if missing-new-line (...) (...)) Obviously I meant "LOOP-FINISH" and "multiple-value-setq"... But one drawback is you need to make variables separately. So LOOP-FINISH or just leave your tagbody as is.
From: Tamas K Papp on 4 Dec 2009 10:37
On Fri, 04 Dec 2009 07:20:07 -0800, fortunatus wrote: > You could replace the tagbody with simple LOOP and LOOP-FINSH, or you > could use the complex LOOP keywords like DO and WHILE. But frankly I > find your TAGBODY and GO to be quite clear as it is, even probably more > clear than the complex loop keywords would be. Otherwise I think your > code is great! > > By the way, would you mind posting what you did for your ECL exec? > Thanks! > > (loop > :do (multiple-value-bind ... ) > :while line > :do (if missing-new-line (...) (...)) See this nice writeup: http://blog.s21g.com/articles/1649 I just fired up an ECL, and used (compile-file "savebody.lisp" :system-p t) (c:build-program "savebody" :lisp-files '("savebody.o")) but apparently you can do much more complex stuff. And the whole executable is really small. ECL just rocks. Tamas |