filerenamer 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGES CHANGED
@@ -1,6 +1,12 @@
1
1
  = filerenamer Changelog
2
2
 
3
- == Master (for 0.0.2)
3
+ == Master (for 0.0.4)
4
+
5
+ == Version 0.0.3
6
+ * Restruct class and module name
7
+ * Add bin/renpad.
8
+
9
+ == Version 0.0.2
4
10
  * Command name changes. rensub, rennum, etc.
5
11
  * Add bin/renpar.
6
12
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.2
1
+ 0.0.3
data/bin/classify1stchar CHANGED
@@ -1,16 +1,18 @@
1
1
  #! /usr/bin/env ruby
2
2
  # coding: utf-8
3
3
 
4
- require "filerenamer/filerenamer.rb"
5
- require "filerenamer/filerenameroptionparser.rb"
4
+ #gem "filerenamer"
5
+ require "filerenamer.rb"
6
+ #require "filerenamer/commander.rb"
7
+ #require "filerenamer/optionparser.rb"
6
8
 
7
9
  OPTIONS = {}
8
- op = FileRenamerOptionParser.new
10
+ op = FileRenamer::OptionParser.new
9
11
  op.parse!(ARGV)
10
- files = ARGV
12
+ #files = ARGV
11
13
 
12
- fr = FileRenamer.new(op.options)
13
- fr.execute(files) do |filename|
14
+ fr = FileRenamer::Commander.new(op.options, ARGV)
15
+ fr.execute do |filename|
14
16
  next if filename.size == 1
15
17
 
16
18
  yomi = `echo #{filename} | nkf -e | kakasi -JH | nkf -w`
data/bin/rennum CHANGED
@@ -4,11 +4,12 @@
4
4
  # 拡張子は保存
5
5
  # -i オプションで開始番号を指定
6
6
  # -p : preserve old name 元のファイル名を残す。foo.txt -> 001-foo.txt
7
- require "filerenamer/filerenamer.rb"
8
- require "filerenamer/filerenameroptionparser.rb"
7
+ require "filerenamer/commander.rb"
8
+ require "filerenamer/optionparser.rb"
9
+
9
10
 
10
11
  OPTIONS = {}
11
- op = FileRenamerOptionParser.new
12
+ op = FileRenamer::OptionParser.new
12
13
  op.on("-i num", "--initial", "Indicate initial number."){ |val|
13
14
  OPTIONS[:init] = val.to_i
14
15
  }
@@ -19,8 +20,8 @@ op.parse!(ARGV)
19
20
 
20
21
  OPTIONS[:init] ||= 0
21
22
 
22
- fr = FileRenamer.new(op.options)
23
- fr.execute(ARGV) do |filename|
23
+ fr = FileRenamer::Commander.new(op.options, ARGV)
24
+ fr.execute do |filename|
24
25
  @counter ||= OPTIONS[:init]
25
26
  if (OPTIONS[:preserve])
26
27
  new_name = sprintf("%03d-%s", @counter, filename)
data/bin/renpad ADDED
@@ -0,0 +1,48 @@
1
+ #! /usr/bin/ruby -w
2
+ # coding: utf-8
3
+
4
+ # Pad 0 onto the first numeric character series in filenames to become the same length.
5
+
6
+ require "pp"
7
+ require "filerenamer/commander.rb"
8
+ require "filerenamer/optionparser.rb"
9
+
10
+ PATTERN = /^(\D*)(\d+)(.*)$/
11
+
12
+ class String
13
+ def num_length
14
+ self =~ PATTERN
15
+ $2.length
16
+ end
17
+ end
18
+
19
+
20
+ OPTIONS = {}
21
+ op = FileRenamer::OptionParser.new
22
+ op.parse!(ARGV)
23
+
24
+ #pp length
25
+ #exit
26
+
27
+ fr = FileRenamer::Commander.new(op.options, ARGV)
28
+
29
+ length ||= fr.files.max_by { |str| str.num_length }.num_length
30
+ #pp length
31
+
32
+ fr.execute do |filename|
33
+ filename =~ PATTERN
34
+ head = $1
35
+ body = $2.to_i
36
+ tail = $3
37
+
38
+ #pp head
39
+ #pp body
40
+ #pp tail
41
+ #pp length
42
+ #puts
43
+
44
+ new_name = head + sprintf("%0#{length}d", body) + tail
45
+ new_name
46
+ end
47
+
48
+
data/bin/renpar CHANGED
@@ -11,15 +11,16 @@ require "rubygems"
11
11
  gem "builtinextension"
12
12
  require "string_split_parens.rb"
13
13
 
14
- require "filerenamer/filerenamer.rb"
15
- require "filerenamer/filerenameroptionparser.rb"
14
+ require "filerenamer/commander.rb"
15
+ require "filerenamer/optionparser.rb"
16
+
16
17
 
17
18
  OPTIONS = {}
18
- op = FileRenamerOptionParser.new
19
+ op = FileRenamer::OptionParser.new
19
20
  op.parse!(ARGV)
20
21
 
21
- fr = FileRenamer.new(op.options)
22
- fr.execute(ARGV) do |filename|
22
+ fr = FileRenamer::Commander.new(op.options, ARGV)
23
+ fr.execute do |filename|
23
24
  extname = File.extname( filename)
24
25
  basename = File.basename(filename).sub(/#{extname}$/, "")
25
26
 
data/bin/renreg CHANGED
@@ -1,14 +1,14 @@
1
1
  #! /usr/bin/env ruby
2
2
  # coding: utf-8
3
3
 
4
- require "filerenamer/filerenamer.rb"
5
- require "filerenamer/filerenameroptionparser.rb"
4
+ require "filerenamer/commander.rb"
5
+ require "filerenamer/optionparser.rb"
6
6
 
7
- op = FileRenamerOptionParser.new
7
+ op = FileRenamer::OptionParser.new
8
8
  op.parse!(ARGV)
9
9
 
10
- fr = FileRenamer.new(op.options)
11
- fr.execute(ARGV) do |filename|
10
+ fr = FileRenamer::Commander.new(op.options, ARGV)
11
+ fr.execute do |filename|
12
12
  new_name = filename.dup
13
13
  new_name.gsub!('a','a'); new_name.gsub!('b','b'); new_name.gsub!('c','c');
14
14
  new_name.gsub!('d','d'); new_name.gsub!('e','e'); new_name.gsub!('f','f');
data/bin/rensub CHANGED
@@ -1,23 +1,22 @@
1
1
  #! /usr/bin/env ruby
2
2
  # coding: utf-8
3
3
 
4
- require "filerenamer/filerenamer.rb"
5
- require "filerenamer/filerenameroptionparser.rb"
4
+ require "filerenamer/commander.rb"
5
+ require "filerenamer/optionparser.rb"
6
6
 
7
7
  OPTIONS = {}
8
- op = FileRenamerOptionParser.new
8
+ op = FileRenamer::OptionParser.new
9
9
  op.on("-g", "--global", "Global substitution."){ OPTIONS[:global] = true }
10
10
  op.on("-r", "--reg-exp", "Regular expression."){ OPTIONS[:regexp] = true }
11
11
  op.parse!(ARGV)
12
12
 
13
13
  old_str = ARGV.shift
14
14
  new_str = ARGV.shift
15
- files = ARGV
16
15
 
17
16
  old_str = /#{old_str}/ if OPTIONS[:regexp]
18
17
 
19
- fr = FileRenamer.new(op.options)
20
- fr.execute(files) do |filename|
18
+ fr = FileRenamer::Commander.new(op.options, ARGV)
19
+ fr.execute do |filename|
21
20
  if OPTIONS[:global]
22
21
  new_name = filename.gsub(old_str, new_str)
23
22
  else
data/filerenamer.gemspec CHANGED
@@ -5,14 +5,14 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "filerenamer"
8
- s.version = "0.0.2"
8
+ s.version = "0.0.3"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["ippei94da"]
12
- s.date = "2012-07-04"
12
+ s.date = "2012-08-20"
13
13
  s.description = "This library provide common dealing to rename many files with safe method. Automatically mkdir if need and rmdir when empty."
14
14
  s.email = "ippei94da@gmail.com"
15
- s.executables = ["classify1stchar", "rennum", "renpar", "renreg", "rensub"]
15
+ s.executables = ["classify1stchar", "rennum", "renpad", "renpar", "renreg", "rensub"]
16
16
  s.extra_rdoc_files = [
17
17
  "LICENSE.txt",
18
18
  "README.rdoc"
@@ -27,22 +27,49 @@ Gem::Specification.new do |s|
27
27
  "VERSION",
28
28
  "bin/classify1stchar",
29
29
  "bin/rennum",
30
+ "bin/renpad",
30
31
  "bin/renpar",
31
32
  "bin/renreg",
32
33
  "bin/rensub",
33
34
  "filerenamer.gemspec",
34
35
  "lib/filerenamer.rb",
35
- "lib/filerenamer/filerenamer.rb",
36
- "lib/filerenamer/filerenameroptionparser.rb",
36
+ "lib/filerenamer/commander.rb",
37
+ "lib/filerenamer/optionparser.rb",
38
+ "test/classify1stchar/123",
39
+ "test/classify1stchar/abc",
40
+ "test/classify1stchar/def",
37
41
  "test/filerenamer/a0.txt",
38
42
  "test/filerenamer/dummy.txt",
39
43
  "test/helper.rb",
44
+ "test/rennum/a",
45
+ "test/rennum/b",
46
+ "test/rennum/c",
47
+ "test/rennum/d",
48
+ "test/renpad/1",
49
+ "test/renpad/10",
50
+ "test/renpad/100",
51
+ "test/renpad/100b",
52
+ "test/renpad/10b",
53
+ "test/renpad/1b",
54
+ "test/renpad/a1",
55
+ "test/renpad/a10",
56
+ "test/renpad/a100",
57
+ "test/renpad/a100b",
58
+ "test/renpad/a10b",
59
+ "test/renpad/a1b",
40
60
  "test/renpar/((ab)(cd(ef))gh(ij)kl(mn).txt",
41
61
  "test/renpar/(ab)(cd(ef)))gh(ij)kl(mn).txt",
42
62
  "test/renpar/(ab)(cd(ef))gh(ij)kl(mn).txt",
43
63
  "test/renpar/(ab)(cd).txt",
44
- "test/test_filerenamer.rb",
45
- "test/test_filerenameroptionparser.rb"
64
+ "test/renreg/a(b)",
65
+ "test/renreg/1",
66
+ "test/renreg/a",
67
+ "test/rensub/a0",
68
+ "test/rensub/a1",
69
+ "test/rensub/b0",
70
+ "test/rensub/b1",
71
+ "test/test_commander.rb",
72
+ "test/test_optionparser.rb"
46
73
  ]
47
74
  s.homepage = "http://github.com/ippei94da/filerenamer"
48
75
  s.licenses = ["MIT"]
data/lib/filerenamer.rb CHANGED
@@ -0,0 +1,4 @@
1
+ require "filerenamer/commander.rb"
2
+ require "filerenamer/optionparser.rb"
3
+
4
+ module FileRenamer; end
@@ -0,0 +1,262 @@
1
+ #! /usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ require "rubygems"
5
+ gem "builtinextension"
6
+ require "string_escape_zsh.rb"
7
+ require "string_width.rb"
8
+
9
+ require "fileutils"
10
+ require "optparse"
11
+ require "filerenamer/optionparser.rb"
12
+
13
+ # ファイル群の名前変更を一括して行うためのクラス。
14
+ # 他のアプリケーションからも使えるけれど、
15
+ # 基本的にはコマンドライン関係の取り扱いを全部まとめてできるようにするもの。
16
+ # そのため、initialize で files を受け取ってしまい、
17
+ # execute の度に指定しなくても良いことにしている。
18
+ # execute の度に指定するようにすると、renpad のように対象全体を見てどうこうする
19
+ # コマンドで不便。
20
+ #
21
+ # MEMO:
22
+ # 一度 temporary directory にリネームして格納し、
23
+ # これをカレントディレクトリからのパスに移動する形を取ると、
24
+ # ディレクトリをまたぐリネームが非常に面倒なことになる。
25
+ # - 他のリネームに依存する場合に順序問題。
26
+ # - 相互依存のデッドロック
27
+ # gem などで提供される temporary directory は
28
+ # 基本的に 抜けたときに削除される筈なので、
29
+ # 途中まで変換してから interrupt されたときに
30
+ # 中にあるファイルごと消される可能性がありそう。
31
+ # そのため、メソッド内で自分で temporary directory を作成する。
32
+ #
33
+ # 没案
34
+ # rename_rule メソッドをデフォルトで例外にしておいて、
35
+ # コマンドスクリプト側で定義するという方法は、
36
+ # メソッドの引数で ArgumentError のもとになり、
37
+ # 自由度が少なくなる。
38
+ # たとえば old_str, new_str を渡したい時と、
39
+ # 更新時刻でリネームしたいときでは必要な引数の数が異なる。
40
+ module FileRenamer
41
+ class Commander
42
+ attr_reader :files
43
+
44
+ class NoRenameRuleError < Exception; end
45
+ class OptionError < Exception; end
46
+
47
+ #:yes と :no が両方 true ならば例外。
48
+ #:copy, :move, :hardlink, :symlink のうち、1つ以下が true。
49
+ #全て nil ならば :move が true になる。
50
+ #:quiet が true ならば自動的に :yes が立てられる。
51
+ #:quiet が true で :no も true ならば例外。
52
+ def initialize(options, files)
53
+ @options = options
54
+
55
+ if (@options[:yes] && @options[:no])
56
+ raise OptionError,
57
+ "Conflict options: --yes and --no."
58
+ end
59
+
60
+ fileProcessModes = []
61
+ [:move, :copy, :hardlink, :symlink].each do |mode|
62
+ fileProcessModes << mode if @options[mode]
63
+ end
64
+ # 1つもなければ :move に。
65
+ @options[:move] = true if fileProcessModes == []
66
+ # 2つ以上あれば例外。
67
+ if fileProcessModes.size > 1
68
+ raise OptionError,
69
+ "File process modes duplicate: #{fileProcessModes.join(", ")}"
70
+ end
71
+ @command = "mv" if @options[:move]
72
+ @command = "cp -r" if @options[:copy]
73
+ @command = "ln" if @options[:hardlink]
74
+ @command = "ln -s" if @options[:symlink]
75
+
76
+ # :quiet ならば自動的に :yes
77
+ @options[:yes] = true if @options[:quiet]
78
+
79
+ # :quiet と同時に :no なら例外
80
+ if @options[:quiet] && @options[:no]
81
+ raise OptionError,
82
+ "Conflict options: --quiet and --no"
83
+ end
84
+
85
+ @files = files
86
+ @files = Dir::glob("*").sort if files.empty?
87
+ end
88
+
89
+ # 変更される名前のリストを表示し、ユーザの指示に従って実行する。
90
+ # ユーザの入力は [y|Y] で始まる文字列ならば実行、
91
+ # それ以外なら実行しない。
92
+ # files は対象のファイル名のリスト。
93
+ # 新しい名前の生成方法をブロックで渡す。
94
+ # ブロックがなければ例外 FileRenamerNoRenameRuleError
95
+ def execute(&block)
96
+ new_names = make_new_names(&block)
97
+
98
+ ok_files, ng_files = check_new_names(new_names)
99
+
100
+ unless @options[:quiet]
101
+ if ok_files.size > 0
102
+ puts "Enable files:"
103
+ max_width = ok_files.keys.max_by{|file| file.width}.width
104
+ ok_files.keys.sort.each do |old|
105
+ printf(" %s %s%s %s\n",
106
+ @command, old, " " * (max_width - old.width), ok_files[old]
107
+ )
108
+ end
109
+ end
110
+
111
+ if ng_files.size > 0
112
+ puts "Unable files:"
113
+ max_width = ng_files.keys.max_by{|file| file.width}.width
114
+ ng_files.keys.sort.each do |old|
115
+ printf(" %s %s%s %s\n",
116
+ @command, old, " " * (max_width - old.width), ng_files[old]
117
+ )
118
+ end
119
+ puts
120
+ end
121
+ end
122
+
123
+ if ok_files.empty?
124
+ puts "Done. No executable files." unless @options[:quiet]
125
+ return
126
+ elsif @options[:no]
127
+ puts "Execute? no"
128
+ return
129
+ end
130
+
131
+ if @options[:yes]
132
+ puts "Execute? yes" unless @options[:quiet]
133
+ elsif (! ask_yes?)
134
+ return
135
+ end
136
+ ok_files.each do |old, new|
137
+ run(old, new)
138
+ end
139
+ end
140
+
141
+ private
142
+
143
+ def make_new_names(&block)
144
+ #pp @files
145
+ results = {}
146
+ @files.each { |i| results[i] = yield i }
147
+ return results
148
+ end
149
+
150
+ # 新しい名前リストを受け取り、問題なくリネームできるものと
151
+ # できないものを選別する。
152
+ # OK, NG の順に2つの配列を返す。
153
+ #
154
+ # 判断基準は、以下の AND 条件。
155
+ # - 新しい名前が古い名前リストに含まれないこと。
156
+ # - 新しい名前が新しい名前リストに1つ(自分)のみしかないこと。
157
+ # - 新しい名前のファイルが既に存在しないこと。
158
+ #
159
+ # 没案
160
+ # 新しい名前が変換されない古い名前であれば、
161
+ # これも NG リストに追加する。(表示用)
162
+ # このルールにすると、変換に順序依存が生じる可能性があり、
163
+ # temporary directory を経由する必要ができる。
164
+ # その結果、リネーム過程で深い temporary directory を
165
+ # 作成したり削除したりしなければならなくなる。
166
+ def check_new_names(old_new_hash)
167
+ ok_files = {}
168
+ ng_files = {}
169
+
170
+ old_new_hash.each do |old, new|
171
+ # 変化がない場合はスルー
172
+ next if old == new
173
+
174
+ # 新しい名前に nil が定義されていてもスルー
175
+ next if new == nil
176
+
177
+ # 既に存在していれば、既に存在するファイルごと NG に登録。
178
+ if FileTest.exist?(new)
179
+ ng_files[old] = new
180
+ ng_files[new] = new
181
+ next
182
+ end
183
+
184
+ # new が複数 重複していれば、 NG。
185
+ if (old_new_hash.values.select{|name| name == new}.size > 1)
186
+ ng_files[old] = new
187
+ next
188
+ end
189
+
190
+ # new が old に含まれていれば、NG。
191
+ if (old_new_hash.keys.include?(new))
192
+ ng_files[old] = new
193
+ next
194
+ end
195
+
196
+ # 上記以外の場合ならば OK
197
+
198
+ ok_files[old] = new
199
+ end
200
+ return ok_files, ng_files
201
+ end
202
+
203
+ # ユーザに判断を求める。
204
+ # 標準入力から文字列を受け取り、
205
+ # [y|Y] から始まる文字列なら true を、
206
+ # それ以外なら false を返す。
207
+ def ask_yes?
208
+ puts "Execute?"
209
+ str = $stdin.gets
210
+ if /^y/i =~ str
211
+ return true
212
+ else
213
+ return false
214
+ end
215
+ end
216
+
217
+ # コマンドを表示しつつ実行。
218
+ # 既にファイルチェックされているはずなので、チェックはしない。
219
+ # 少なくとも当面は。
220
+ # ディレクトリが必要ならここで作成。
221
+ # ディレクトリが不要になればここで削除。
222
+ # 本来ここでない方が分かり易い気もするのだが、ここに追加するのが一番簡単なので。
223
+ def run(old, new)
224
+ # 変換先のディレクトリがなければ生成
225
+ #pp old, new; exit
226
+ paths(new).each do |directory|
227
+ unless FileTest.exist?(directory)
228
+ puts " make directory: #{directory}" unless @options[:quiet]
229
+ FileUtils.mkdir_p(directory)
230
+ end
231
+ end
232
+
233
+ # 変換の実行
234
+ command = " #{@command} #{old.escape_zsh} #{new.escape_zsh}"
235
+ puts command unless @options[:quiet]
236
+ system(command)
237
+
238
+ # 変換元のディレクトリが空になっていれば削除
239
+ dirs = paths(old)
240
+ dirs.reverse.each do |directory|
241
+ if Dir.entries(directory).size == 2 # . と .. のみ
242
+ puts " remove directory: #{directory}" unless @options[:quiet]
243
+ Dir.rmdir(directory)
244
+ end
245
+ end
246
+ end
247
+
248
+ # パスに含まれるディレクトリ名を、階層ごとに切り分けたソート済配列にして返す。
249
+ # 最後の要素は含まない。
250
+ # e.g. foo/bar/baz.txt => ["foo", "foo/bar"]
251
+ # e.g. /foo/bar/baz.txt => ["/foo", "foo/bar"]
252
+ def paths(path)
253
+ dirs = path.split("/")
254
+ results = []
255
+ (dirs.size - 1).times do |i|
256
+ results << dirs[0..i].join("/")
257
+ end
258
+ results.delete_at(0) if results[0] == ""
259
+ return results
260
+ end
261
+ end
262
+ end