青木です。
タブを扱うライブラリが標準に欲しいので、rough の tabs.rb を
レビューしました。基本的には現状で満足ですが、数点不満があります。
1. (書きかたとして) non-bang メソッドより bang メソッドのが
明らかに高速に見えるのが気に入らない。
2. オリジナルの expand はタブと非タブが交互に出てくる
行を処理させると異常に遅くなることがある。
文句を言うだけでは何なので別の実装を書きました。
書いたのを末尾に添付しときます。
速度は、オリジナルと比較すると
* 最悪のケースではオリジナルより圧倒的に速い
* 行頭にタブが集中する最もありがちなケースではほとんど同じ
という感じです。
ついでに bang 系メソッドは Tabs モジュールからは捨てて
String で実装するようにしてみました。引数を破壊的に変更する
のはわかりにくいと思うからです。どうせ String にメソッドを
追加するのだから、破壊的メソッドはこちらで実装されていれば
十分でしょう。
なお全体的に破壊的メソッドが非破壊的メソッドより遅くなるように
実装しました。
以下は弱めの意見です。
* Text モジュールとかの下に置きたい人は多そうだ。
(個人的にはトップレベルでも気になりません)
* String のメソッドにするときは expand_tabs のがいい?
* tabify/untabify という別名が欲しいかもしれない。
* detab/entab も欲しいかもしれない。
参考
google(expandtab) = 13900
google(expandtabs) = 3380
google(expand tab) = 1340000
google(unexpandtab) = 0
google(unexpandtabs) = 18
google(unexpand tab) = 3290
google(untabify) = 8260
google(tabify) = 4680
google(detab) = 7340
google(entab) = 3130
-------------------------------------------------------------------
青木峰郎
#!/usr/bin/env ruby
#
# Copyright (c) 2001 Akinori MUSHA <knu / iDaemons.org>
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
# $Idaemons: /home/cvs/rb/tabs.rb,v 1.2 2001/05/30 09:37:45 knu Exp $
# $Id: tabs.rb,v 1.1 2001/06/01 18:58:29 knu Exp $
module Tabs
DEFAULT_TABSTOP = 8
def expand(text, tabstop = DEFAULT_TABSTOP)
text.map {|line|
add = 0
line.sub(/\A\t+/) {|s| ' ' * (tabstop * s.length) }.gsub(/\t/) {
len = tabstop - ($~.begin(0) + add) % tabstop
add += len - 1
' ' * len
}
}.join('')
end
def unexpand(text, tabstop = DEFAULT_TABSTOP)
text.gsub(/^([ \t]+)/) {
t, s = expand($1, tabstop).length.divmod(tabstop)
"\t" * t + ' ' * s
}
end
def unexpand_all(text, tabstop = DEFAULT_TABSTOP)
expand(text, tabstop).map { |line|
len = line.length
mod = len % tabstop
(len - mod - tabstop).step(0, -tabstop) { |i|
s = line[i,tabstop]
if s.sub!(/ +\z/, "\t")
line[i,tabstop] = s
end
}
}.join('')
end
module_function :expand, :unexpand, :unexpand_all
end
class String
def expand(tabstop = Tabs::DEFAULT_TABSTOP)
Tabs::expand(self, tabstop)
end
def expand!(tabstop = Tabs::DEFAULT_TABSTOP)
replace(Tabs::expand(self, tabstop))
end
def unexpand(tabstop = Tabs::DEFAULT_TABSTOP)
Tabs::unexpand(self, tabstop)
end
def unexpand!(tabstop = Tabs::DEFAULT_TABSTOP)
replace(Tabs::unexpand(self, tabstop))
end
def unexpand_all(tabstop = Tabs::DEFAULT_TABSTOP)
Tabs::unexpand_all(self, tabstop)
end
def unexpand_all!(tabstop = Tabs::DEFAULT_TABSTOP)
replace(Tabs::unexpand_all(self, tabstop))
end
end