class FileRenamer::Commander

ファイル群の名前変更を一括して行うためのクラス。 他のアプリケーションからも使えるけれど、 基本的にはコマンドライン関係の取り扱いを全部まとめてできるようにするもの。 そのため、initialize で files を受け取ってしまい、 execute の度に指定しなくても良いことにしている。 execute の度に指定するようにすると、renpad のように対象全体を見てどうこうする コマンドで不便。

MEMO: 一度 temporary directory にリネームして格納し、 これをカレントディレクトリからのパスに移動する形を取ると、 ディレクトリをまたぐリネームが非常に面倒なことになる。

gem などで提供される temporary directory は 基本的に 抜けたときに削除される筈なので、 途中まで変換してから interrupt されたときに 中にあるファイルごと消される可能性がありそう。 そのため、メソッド内で自分で temporary directory を作成する。

没案

rename_rule メソッドをデフォルトで例外にしておいて、
コマンドスクリプト側で定義するという方法は、
メソッドの引数で ArgumentError のもとになり、
自由度が少なくなる。
たとえば old_str, new_str を渡したい時と、
更新時刻でリネームしたいときでは必要な引数の数が異なる。

Attributes

files[R]

Public Class Methods

files(strs) click to toggle source
# File lib/filerenamer/commander.rb, line 48
def self.files(strs)
  if strs.empty?
    return Dir::glob("*").sort
  else
    return strs
  end
end
new(options, files) click to toggle source

:yes と :no が両方 true ならば例外。 :copy, :move, :hardlink, :symlink, :git のうち、1つ以下が true。 全て nil ならば :move が true になる。 :quiet が true ならば自動的に :yes が立てられる。 :quiet が true で :no も true ならば例外。

# File lib/filerenamer/commander.rb, line 61
def initialize(options, files)
  @options = options

  if (@options[:yes] && @options[:no])
    raise OptionError, "Conflict options: --yes and --no."
  end

  fileProcessModes = []
  [:move, :copy, :hardlink, :symlink, :git].each do |mode|
    fileProcessModes << mode if @options[mode]
  end
  # mode が1つもなければ :move に。
  @options[:move] = true if fileProcessModes == []
  # 2つ以上あれば例外。
  if fileProcessModes.size > 1
    raise OptionError,
      "File process modes duplicate: #{fileProcessModes.join(", ")}"
  end

  @mode = :move      if @options[:move]
  @mode = :copy      if @options[:copy]
  @mode = :hardlink  if @options[:hardlink]
  @mode = :symlink   if @options[:symlink]
  @mode = :git       if @options[:git]

  case @mode
  when :move     ; then;  @command = "mv"
  when :copy     ; then;  @command = "cp -r"
  when :hardlink ; then;  @command = "ln"
  when :symlink  ; then;  @command = "ln -s"
  when :git      ; then;  @command = "git mv"
  else
    raise ModeError, "Unknown mode: #{@mode}"
  end

  @options[:yes] = true if @options[:quiet] # :quiet ならば自動的に :yes

  if @options[:quiet] && @options[:no] # :quiet と同時に :no なら例外
    raise OptionError, "Conflict options: --quiet and --no"
  end

  @files = self.class.files(files)
end

Public Instance Methods

execute(&block) click to toggle source

変更される名前のリストを表示し、ユーザの指示に従って実行する。 ユーザの入力は [y|Y] で始まる文字列ならば実行、 それ以外なら実行しない。 新しい名前の生成方法をブロックで渡す。 ブロックがなければ例外 FileRenamerNoRenameRuleError

# File lib/filerenamer/commander.rb, line 110
def execute(&block)
  new_names = make_new_names(&block)

  if @mode == :move || @mode == :git
    ro = FileRenamer::RenameOrderer.new(new_names)
    ok_files = ro.rename_processes
    ng_files = ro.unable_processes
  else
    ok_files = []
    ng_files = []
    new_names.each do |old, new|
      if File.exist? new
        ng_files << [old, new]
      else
        ok_files << [old, new]
      end
    end
  end

  show_conversions(ok_files, "Enable files:")
  #show_conversions(ng_files, "Unable files:")

  (puts "Execute? no"; return) if @options[:no]

  if ok_files.empty?
    puts "Done. No executable files." unless @options[:quiet]
    return
  end

  if @options[:yes]
    puts "Execute? yes" unless @options[:quiet]
  elsif (! ask_yes?)
    return
  end

  convert( ok_files)
end

Private Instance Methods

ask_yes?() click to toggle source

ユーザに判断を求める。 標準入力から文字列を受け取り、

y|Y

から始まる文字列なら true を、

それ以外なら false を返す。

# File lib/filerenamer/commander.rb, line 205
def ask_yes?
  puts "Execute?"
  if /^y/i =~ $stdin.gets
    return true
  else
    return false
  end
end
convert(conversions) click to toggle source

コマンドを表示しつつ実行。 既にファイルチェックされているはずなので、チェックはしない。 ディレクトリが必要になればここで作成。 ディレクトリが不要になればここで削除。

# File lib/filerenamer/commander.rb, line 218
def convert(conversions)
  #tmpdir = Dir.mktmpdir('rename', './')

  #int_paths = {} #intermediate paths
  #conversions.each { |old, new| int_paths[old] = tmpdir + '/' + old }
  #conversions.each { |old, new| int_paths[old] = tmpdir + '/' + old }
  #int_paths.each   { |old, int| transplant(old, int) }
  #int_paths.each   { |old, int| rmdir_p old }

  conversions.each { |old, new| transplant(old, new) }
  #int_paths.each   { |old, int| rmdir_p int }
end
make_new_names() { |i| ... } click to toggle source
# File lib/filerenamer/commander.rb, line 164
def make_new_names(&block)
  results = {}
  @files.each { |i| results[i] = yield i }
  return results
end
paths(path) click to toggle source

パスに含まれるディレクトリ名を、階層ごとに切り分けたソート済配列にして返す。 最後の要素は含まない。 e.g. foo/bar/baz.txt => [“foo”, “foo/bar”] e.g. /foo/bar/baz.txt => [“/foo”, “foo/bar”]

# File lib/filerenamer/commander.rb, line 261
def paths(path)
  dirs = path.split("/")
  results = []
  (dirs.size - 1).times do |i|
    results << dirs[0..i].join("/")
  end
  results.delete_at(0) if results[0] == ""
  return results
end
rmdir_p(path) click to toggle source
# File lib/filerenamer/commander.rb, line 271
def rmdir_p(path)
  Pathname.new(path).ascend do |p_path|
    begin
      Dir.rmdir p_path
    rescue
      break
    end
  end
end
show_conversions(conversions, heading) click to toggle source
# File lib/filerenamer/commander.rb, line 150
def show_conversions(conversions, heading)
  #STDOUT.puts conversions
  return if conversions.size == 0
  return if @options[:quiet]
  puts heading
  max_width = conversions.max_by{|old, new| old.width}[0].width
  conversions.each do |old, new|
    printf("  %s %s%s %s\n",
      @command, old, " " * (max_width - old.width), new
    )
  end
  puts
end
transplant(src_path, tgt_path) click to toggle source

Move from root to root. src_path and tgt_path are paths.

# File lib/filerenamer/commander.rb, line 233
def transplant(src_path, tgt_path)
  #pp src_path, tgt_path;exit
  # 変換先のディレクトリがなければ生成
  tgt_dir = File.dirname(tgt_path)
  unless FileTest.exist?(tgt_dir)
    #puts "  make directory: #{tgt_dir}" unless @options[:quiet]
    FileUtils.mkdir_p(tgt_dir)
  end

  # 変換の実行
  command = "  #{@command} #{src_path.escape_zsh} #{tgt_path.escape_zsh}"
  #puts command unless @options[:quiet]
  system(command)

  ##STDIN.gets
  # 変換元のディレクトリが空になっていれば削除
  src_dir = File.dirname(src_path)
  #pp Dir.entries(src_dir)
  if Dir.entries(src_dir).size == 2 # . と .. のみ
    #puts "  remove directory: #{src_dir}" unless @options[:quiet]
    rmdir_p(src_dir)
  end
end