2006/09/30

GaucheにもRubyみたいなARGFがほしい。
正確にいうと、ほしがっているのは僕じゃないんだけど、確かにARGFみたいな仕組みがあると使い捨てのテキストフィルタを書いたりするのは簡単になりそうだ。Gaucheのマニュアルではmainを使うことが推奨されているけど、気楽で泥臭いスクリプトを作りたいことだってある。

RubyのリファレンスマニュアルのARGFの項目には「スクリプトに指定した引数 (ARGV を参照) をファイル名とみなして、それらのファイルを連結した 1 つの仮想ファイルを表すオブジェクト」とある。その説明がARGFのすべてなら、Gaucheに用意されている仮想ポートで似たようなものが作れるかもしれない。

でっちあげてみた。引数として渡されたファイルのテキストをいったんバッファに吐き出しているので、Rubyのように*argv*のリストが変更されるわけではない。バッファが空なら標準入力を読む。
#! /usr/local/bin/gosh

(use gauche.vport)
(use srfi-13)

(define argv-str
(let R ((files *argv*) (str ""))
(if (null? files)
""
(call-with-input-file (car files)
(lambda (port)
(let ((joined-string
(string-join (list str (port->string port)) "" 'strict-infix)))
(if (not (null? (cdr files)))
(R (cdr files) joined-string)
joined-string)))))))

(define (getc str)
(cond ((> (string-length str) 0)
(let ((c (string-ref str 0)))
(set! argv-str (string-drop str 1))
c))
((= (string-length str) 0)
"")
(else
(error "Out of Range -- getc"))))

(define (argf thunk)
(if (string-null? argv-str)
(with-input-from-port (current-input-port)
thunk)
(with-input-from-port
(make <virtual-input-port>
:getc (lambda () (getc argv-str)))
thunk)))

;;; test
(argf (lambda ()
(port-for-each
(lambda (line) (print (regexp-replace #/hello/ line "damn")))
read-line)))


実行結果
$ cat test.txt
hello world
this is argf test
$ ./argf.scm test.txt test.txt
damn world
this is argf test
damn world
this is argf test
$ cat test.txt test.txt | ./argf.scm
damn world
this is argf test
damn world
this is argf test

作ってから必ず思う、すでにこんなのは誰かが作っているんではないだろうか。

No comments :