Class RecursiveUtils::Sed
In: recursiveutils.rb
Parent: Object

Summary

再帰処理機能を実装した Ruby 版 sed.

Example

  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../ を含む行に対してブロックの処理を行う

Methods

Public Methods

初期化:new
置換処理:sed, gsub, upcase, downcase
設定:size=, set_legend

Private Methods

ファイル操作:open_output, change_stat
設定:correct_flag
その他:prepare_pattern, count_line, puts_diff

Reference Tool

rsed2.rb:www.ep.sci.hokudai.ac.jp/~morikawa/ruby/rsed2/SIGEN_PUB.htm

Methods

Included Modules

FullPathList::Flag LocaleFilter::PrintWrapper

Constants

DEF_SIZE = 1024   文字コード判定時に読み込むファイルサイズの初期値 (byte).
MAX_SIZE = DEF_SIZE * 1024   文字コード判定時に読み込むファイルサイズの上限値 (byte).

Attributes

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]  冗長なメッセージを出力する.

Public Class methods

新規オブジェクトを作成する. オプションの意味は FullPathList::FlagSedAttributes を参照する事.

[Source]

# 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

Public Instance methods

path 中の大文字を小文字に置換する.

[Source]

# File recursiveutils.rb, line 917
    def downcase(*path)
      sed(path, /[A-Z]/){ |i| i.downcase }
    end

path 中の正規表現 from に一致する文字列を to に置換する.

[Source]

# 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 を返す.

[Source]

# 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 等の設定用メソッド.

[Source]

# 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 を返す.

[Source]

# 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 中の小文字を大文字に置換する.

[Source]

# File recursiveutils.rb, line 912
    def upcase(*path)
      sed(path, /[a-z]/){ |i| i.upcase }
    end

Private Instance methods

ファイル target の mode, owner を src の値に変更する. @backup@forcetrue の場合はファイル名の変更も行う.

[Source]

# 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

オプションの固定値設定, 衝突修正, 補正処理を行う.

[Source]

# 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

str の行数を返す.

[Source]

# File recursiveutils.rb, line 1012
    def count_line(str)
      str.scan(/$/).size
    end

path を元に変換結果を出力するファイルをオープンし, パス, IO オブジェクトを Hash list に追加して返す.

[Source]

# 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 をオプションに応じて変換して返す.

[Source]

# 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

str に行番号をつけて表示する. 最後に表示した行番号を返す.

[Source]

# File recursiveutils.rb, line 1017
    def puts_diff(str, figure, num, message)
      str.each_line do |line|
        n = sprintf("%*d", figure, num)
        @filter.puts " |#{n}: #{message} #{line}"
        num += 1
      end
      return num
    end

[Validate]