Class | RecursiveUtils::Sed |
In: |
recursiveutils.rb
|
Parent: | Object |
再帰処理機能を実装した Ruby 版 sed.
require 'recursiveutils' obj = RecursiveUtils::Sed.new obj.gsub('f..', 'bar', 'foo.txt') # => 対象ファイル中の文字列 /f../ を "bar" に置換する obj.upcase('foo.txt') # => 対象ファイル中の小文字を大文字に置換する obj.recursive = true obj.gsub('text', 'テキスト', '/usr/local/src/') # => 対象ディレクトリ以下のファイル全てに対して実行する obj = RecursiveUtils::Sed.new obj.code = 'euc' obj.gsub('ほげ', 'hoge', 'hoge.txt') # => 出力文字コードを "EUC" に設定して実行する obj = RecursiveUtils::Sed.new obj.sed('f..', 'hoge.txt') { |i| i.gsub('abc', 'def').downcase } # => "hoge.txt" の /f../ を含む行に対してブロックの処理を行う
初期化: | new |
置換処理: | sed, gsub, upcase, downcase |
設定: | size=, set_legend |
ファイル操作: | open_output, change_stat |
設定: | correct_flag |
その他: | prepare_pattern, count_line, puts_diff |
rsed2.rb: | www.ep.sci.hokudai.ac.jp/~morikawa/ruby/rsed2/SIGEN_PUB.htm |
DEF_SIZE | = | 1024 | 文字コード判定時に読み込むファイルサイズの初期値 (byte). | |
MAX_SIZE | = | DEF_SIZE * 1024 | 文字コード判定時に読み込むファイルサイズの上限値 (byte). |
auto_conv | [RW] | 文字コード変換時に事前の判定結果を使わずに自動判定で処理を行う. |
backup | [RW] | 元ファイル名に @extension を追加して バックアップを取り, 元ファイルを上書きする. |
code | [RW] | 文字コード名. 設定されていると文字コード判定結果を上書きする. |
escape | [RW] | 変換パターンの正規表現のメタ文字を保護する. |
expand | [RW] | "." が改行文字にもマッチする. |
extension | [RW] | バックアップファイルの拡張子. デフォルトは .bak. |
file_list | [RW] | 変換されるファイルのリストのみを表示する. |
filter | [R] | LocaleFilter オブジェクト. 主に画面表示用. |
force | [RW] | ファイルを上書きする (@backup より優先される). |
legend | [R] | 凡例. set_legend で自動生成される. |
legend_after | [R] | 変換後に対応する凡例. set_legend で自動生成される. |
legend_before | [R] | 変換前に対応する凡例. set_legend で自動生成される. |
multi | [RW] | 複数行の置換を有効にする. |
noop | [RW] | 変換処理を行わない. |
protect | [RW] | 一時ファイルやバックアップファイルの作成時に 既存のファイルを上書きしない. |
quiet | [RW] | メッセージを抑制する (@verbose より優先される). |
sep_list | [R] | リスト間のセパレータ. set_legend で自動生成される. |
sep_path | [R] | パス間のセパレータ. set_legend で自動生成される. |
size | [R] | 文字コード判定時に読み込むファイルサイズ (byte). デフォルトは DEF_SIZE. 上限は MAX_SIZE. |
verbose | [RW] | 冗長なメッセージを出力する. |
新規オブジェクトを作成する. オプションの意味は FullPathList::Flag と Sed の Attributes を参照する事.
# File recursiveutils.rb, line 697 def initialize(opts = {}) set_flag(opts) @noop = opts[:noop] @file_list = opts[:file_list] @backup = opts[:backup] @extension = opts[:extension] @force = opts[:force] @protect = opts[:protect] @multi = opts[:multi] @expand = opts[:expand] @escape = opts[:escape] @verbose = opts[:verbose] @quiet = opts[:quiet] @code = opts[:code] @auto_conv = opts[:auto_conv] self.size = opts[:size] || DEF_SIZE @filter = LocaleFilter.new set_legend(opts[:legend_before], opts[:legend_after]) end
path 中の大文字を小文字に置換する.
# File recursiveutils.rb, line 917 def downcase(*path) sed(path, /[A-Z]/){ |i| i.downcase } end
path 中の正規表現 from に一致する文字列を to に置換する.
# File recursiveutils.rb, line 895 def gsub(from, to, *path) to_code = @filter.japanese?(to) @filter.input = @input_code p "from: #{from}, to: #{to}, code: #{to_code}" if @debug flag = 0 flag |= Regexp::IGNORECASE if @casefold flag |= Regexp::MULTILINE if @expand from = Regexp.new(prepare_pattern(from), flag, 'e') to = prepare_pattern(to) p "from: #{from}, to: #{to}" if @debug sed(path, from, to_code){ |i| i.gsub(from, to) } end
配列 list を元に作成した処理対象中の pattern にブロックの処理を行う. @force, @backup, @noop の値が全て nil の場合は 元の名前にアンダースコアを付加したバックアップファイルに出力する. 一つでも処理が成功したファイルがあれば true を返す.
# File recursiveutils.rb, line 739 def sed(list, pattern, preset_code = nil) # :yields: target raise LocalJumpError, 'Error: No block given.' unless block_given? correct_flag() path_list = FullPathList.get(flag2hash, list) if path_list.empty? warn 'No files will be converted.' unless @quiet || @file_list return false end lf = LocaleFilter.new buffer = "\0" * @size guess = Hash.new status = false unless @noop output = Hash.new output_lf = LocaleFilter.new output_lf.input = 'euc' end unless @quiet puts "locale: #{LOCALE_NAME}", "guess size: #{@size} byte" if @verbose puts @sep_path, @legend, @sep_path unless @file_list end path_list.each do |path| puts '', path, @sep_list if @verbose begin io = open(path) puts " |open: #{path}" if @debug io.read(@size, buffer) guess.replace(lf.guess(buffer)) unless file_code = lf.text_code?(guess[:txt]) warn ' + Warning: Binary files are not supported.' if @warning next end io.rewind if @code file_code = @code elsif preset_code && lf.ascii_code?(file_code) file_code = preset_code end lf.input = file_code unless @auto_conv puts " |code: #{file_code}", " |line: #{guess[:line]}" if @verbose if @multi file = lf.toeuc(io.read) unless flag_line = lf.lf?(guess[:code]) file = lf.tolf(file) puts ' |Convert to LF' if @verbose end flag_match = file.match(pattern) else sep_line = guess[:code] || $/ flag_match = false io.each_line(sep_line) do |line| flag_match ||= (lf.toeuc(line) =~ pattern) end figure = io.lineno.to_s.length io.rewind end unless flag_match puts ' |Nothing to match it.' if @verbose next end if @file_list STDOUT.puts path status = true next end puts "\n#{path}: #{file_code}", @sep_list unless @quiet || @verbose if @noop warn ' + Warning: Write permission denied.' unless @quiet || test(?w, path) else open_output(output, path) output_lf.locale = file_code output_lf.device = output[:io] puts " |output: #{output_lf.inspect}" if @debug end if @multi unless @quiet figure = count_line(file).to_s.length diff = 0 file.gsub(pattern) do |str| i = count_line($`) j = puts_diff(str, figure, i, @legend_before) k = puts_diff(yield(str), figure, i - diff, @legend_after) diff = j - k end end unless @noop file = yield(file) unless flag_line file = lf.convert_line(file, guess[:line]) puts " |Restore to #{guess[:line]}" if @verbose end output_lf.print file end else io.each_line(sep_line) do |line| line = lf.toeuc(line) n = sprintf("%*d", figure, io.lineno) unless @quiet if line =~ pattern @filter.puts " |#{n}: #{@legend_before} #{line}" unless @quiet line = yield(line) @filter.puts " |#{n}: #{@legend_after} #{line}" unless @quiet puts " *#{n}: #{file_code}" if @debug else @filter.puts " |#{n}: #{line}" if @debug end output_lf.print line unless @noop end end io.close puts " |close: #{path}" if @debug unless @noop output[:io].close puts " |close: #{output[:path]}" if @verbose change_stat(path, output[:path]) end puts @sep_path unless @quiet status = true rescue warn (@verbose ? ' *' : "#{path}\n"), $! unless @quiet ensure if io unless io.closed? io.close puts " |close: #{path} (ensure)" if @debug puts @sep_path if @verbose end else puts @sep_path unless @quiet end end end return status end
@legend 等の設定用メソッド.
# File recursiveutils.rb, line 727 def set_legend(before, after) @legend_before = before ? before.to_s : '---' @legend_after = after ? after.to_s : '+++' @legend = " | #{@legend_before} : before, #{@legend_after} : after |" @sep_path = ' +' + ('-' * (@legend.length - 3)) + '+' @sep_list = @sep_path.gsub('-', '=') end
@size の設定用メソッド. 0 < num < MAX_SIZE の範囲外だった場合は例外 ArgumentError を返す.
# File recursiveutils.rb, line 719 def size=(num) num = Integer(num) raise ArgumentError, "#{num}: Invalid size." if num < 1 raise ArgumentError, "Too big (Max: #{MAX_SIZE} byte)." if num > MAX_SIZE @size = num end
path 中の小文字を大文字に置換する.
# File recursiveutils.rb, line 912 def upcase(*path) sed(path, /[a-z]/){ |i| i.upcase } end
ファイル target の mode, owner を src の値に変更する. @backup か @force が true の場合はファイル名の変更も行う.
# File recursiveutils.rb, line 1028 def change_stat(src, target) stat = File::Stat.new(src) File.chmod(stat.mode, target) begin File.chown(stat.uid, stat.gid, target) rescue if @warning file = (@backup || @force) ? src : target warn %Q( + Warning: Owner and group of "#{file}" is not preserved.) end end if @force File.rename(target, src) puts ' |', " |rename: #{target} => #{File.basename(src)}" if @verbose elsif @backup if @protect && test(?e, src + @extension) warn " + Warning: #{src}#{@extension}: Already exists." if @warning num = 1 while test(?e, src + '_' + num.to_s + @extension) warn " + Warning: #{src}_#{num}#{@extension}: Already exists." if @warning num += 1 end bk_name = '_' + num.to_s + @extension else bk_name = @extension end File.rename(src, src + bk_name) File.rename(target, src) puts ' |', " |backup: #{src} => #{File.basename(src)}#{bk_name}" if @verbose end end
オプションの固定値設定, 衝突修正, 補正処理を行う.
# File recursiveutils.rb, line 924 def correct_flag @file_type = 'f' @readable = true @writable = true unless @noop @file_list = false if @quiet if @file_list @noop = true @writable = true end if @quiet || @file_list @debug = false @verbose = false @warning = false elsif @debug @verbose = true @warning = true end @code = @filter.japanese_code?(@code) @extension = '.' + (@extension ? @extension.to_s : 'bak') @filter.locale = LOCALE_NAME @filter.input = 'euc' end
path を元に変換結果を出力するファイルをオープンし, パス, IO オブジェクトを Hash list に追加して返す.
# File recursiveutils.rb, line 974 def open_output(list, path) unless test(?w, File.dirname(path)) raise Errno::EPERM, 'Write permission denied (directory).' end num = '' retry_count = 5 begin list[:path] = path + '_' if @protect while test(?e, list[:path] + num.to_s) warn " + Warning: #{list[:path]}#{num}: Already exists." if @warning num = num.to_i + 1 end flag = File::WRONLY | File::CREAT | File::EXCL else flag = 'w' end list[:io] = open(list[:path] << num.to_s, flag) rescue warn ' + Warning: ', $! if @warning num = num.to_i + 1 retry_count -= 1 if retry_count < 1 warn ' + Give up.' if @warning raise end retry end puts " |open: #{list[:path]}" if @verbose return list end
pattern をオプションに応じて変換して返す.
# File recursiveutils.rb, line 951 def prepare_pattern(pattern) str = @filter.toeuc(pattern) case when @escape str = Regexp.escape(str, 'e') when @multi str = @filter.tolf(@filter.expand_line(str)) when ! @quiet && @filter.guess_line(str)[:code] if @filter.japanese_code?(LOCALE_NAME) warn "Warning: 変換パターンに改行コードが含まれています.\n", '正しく動作させたい場合は複数行処理オプションを指定して下さい.' else warn "Warning: The conversion pattern contains the line feed code.\n", 'Please specify multi-line option when you want to operate correctly.' end end return str end