regren 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a26b9baec5b659d3278b45d2acc2caecbc8bda43
4
+ data.tar.gz: 64cd2f64e3de3f2713af1e5775353611a9ce2ecf
5
+ SHA512:
6
+ metadata.gz: 18639dd14b1249adaa9cdb54c584f18e2afd1f2f722092211016626c9cf5e3b062cfc86f51d7e5c93467d43423c4c86eb6194fb4d239d2eb7fcf85d597bfee0d
7
+ data.tar.gz: 11be53f33fd9e4d99af1a2df2f9b036787a1c0a3a319466232718f02866d52dd683364901368a29c28766ae4b78655eede050faa7378cce8949abba60a4f39e2
data/bin/regren ADDED
@@ -0,0 +1,94 @@
1
+ #!/bin/env ruby
2
+
3
+ $:.unshift(File.expand_path('../../lib', __FILE__))
4
+
5
+ require 'optparse'
6
+ require 'folder_contents_history'
7
+ require 'filename_history'
8
+
9
+ def string_prompt(prompt = '>')
10
+ begin
11
+ print("#{prompt} ")
12
+ gets.chomp
13
+ rescue Interrupt
14
+ exit 130
15
+ end
16
+ end
17
+
18
+ def prompt?(text, options = {})
19
+ begin
20
+ default = options[:yes]
21
+ return default if options[:quiet]
22
+ opts = default ? "[Y/n]" : "[y/N]"
23
+ while true
24
+ print "#{text} #{opts} "
25
+ c = STDIN.gets.chomp!
26
+ if c.downcase == 'y' or (default and c =='')
27
+ return true
28
+ elsif c.downcase == 'n' or (not default and c =='')
29
+ return false
30
+ end
31
+ end
32
+ rescue Interrupt
33
+ exit 130
34
+ end
35
+ end
36
+
37
+ options = { mode: :rename }
38
+ optparse = OptionParser.new do |opts|
39
+ opts.on('-b','--backup','Make backup') { options[:backup] = true }
40
+ opts.on('-B','--new-backup','Start backups anew, implies --backup') do
41
+ options[:new_backup] = true
42
+ options[:backup] = true
43
+ end
44
+ opts.on('-n','--dry-run','Simulate') { options[:dry_run] = true }
45
+ opts.on('-y','--yes','Assume yes as default') { options[:yes] = true }
46
+ opts.on('-q','--quiet','Do not print anything') { options[:quiet] = true }
47
+ opts.on('-r','--restore','Restore backup') { options[:mode] = :restore }
48
+ opts.on('-R','--reapply','Reapply history') { options[:mode] = :reapply }
49
+ opts.on('-f','--file FILE', "File for backup/restore") { |file| options[:history_file] = file }
50
+ opts.on('-H','--history', "Show history for file") { options[:mode] = :history }
51
+ opts.on('-h','--help', "Print help") do
52
+ puts opts
53
+ exit
54
+ end
55
+ end
56
+ optparse.parse!
57
+ options[:history_file] ||= '.backup'
58
+
59
+ if options[:mode] == :rename
60
+ if ARGV.length != 2
61
+ options[:regexp] = Regexp.compile(string_prompt)
62
+ options[:replacement] = string_prompt
63
+ else
64
+ options[:regexp] = Regexp.compile(ARGV[0])
65
+ options[:replacement] = ARGV[1]
66
+ end
67
+ end
68
+
69
+ if File.exists?(options[:history_file]) && !options[:new_backup]
70
+ history = FolderContentsHistory.new_from_history(options[:history_file])
71
+ else
72
+ history = FolderContentsHistory.new
73
+ end
74
+
75
+ history.load_entries
76
+
77
+ case options[:mode]
78
+ when :history
79
+ history.show_history(ARGV.empty? ? Dir["*"] : ARGV)
80
+ when :restore
81
+ history.plan_rollbacks
82
+ when :rename
83
+ history.plan_renames(options[:regexp], options[:replacement])
84
+ require 'pry'; binding.pry
85
+ when :reapply
86
+ history.plan_reapplication('.')
87
+ end
88
+ history.log unless options[:quiet]
89
+ unless options[:dry_run] || options[:mode] == :history
90
+ if !history.changed.empty? && prompt?("Execute the rename?", options)
91
+ history.log_backup(options[:history_file]) if options[:backup]
92
+ history.execute
93
+ end
94
+ end
@@ -0,0 +1,75 @@
1
+ class FileNameHistory
2
+ def initialize(name, history = [])
3
+ @history = history.empty? ? [name] : history
4
+ @current = name
5
+ end
6
+
7
+ def to_hash
8
+ {
9
+ @history.last => @history
10
+ }
11
+ end
12
+
13
+ def self.new_from_history(history)
14
+ return FileNameHistory.new(history.last,
15
+ history)
16
+ end
17
+
18
+ def present?
19
+ File.exists?(@current)
20
+ end
21
+
22
+ def original
23
+ @history.first
24
+ end
25
+
26
+ def history
27
+ @history
28
+ end
29
+
30
+ def last_name
31
+ @history.last
32
+ end
33
+
34
+ def plan_rename(regexp, replacement)
35
+ new_name = @current.gsub(regexp, replacement)
36
+ @history << new_name if new_name != current
37
+ end
38
+
39
+ def plan_rollback
40
+ @history << original
41
+ end
42
+
43
+ def was_named?(name)
44
+ @history.include? name
45
+ end
46
+
47
+ def plan_reapplication(name)
48
+ @current = name
49
+ end
50
+
51
+ def changed?
52
+ @history.last != @current
53
+ end
54
+
55
+ def rename
56
+ File.rename("#{@current}", "#{@history.last}")
57
+ @current = @history.last
58
+ end
59
+
60
+ def print_history(where = $stdout)
61
+ where.puts(@current)
62
+ where.print "-> #{@history.join("\n-> ")}\n\n"
63
+ end
64
+
65
+ def log(where = $stdout)
66
+ return unless changed?
67
+ where.puts(@current)
68
+ where.puts("-> #{@history.last}")
69
+ end
70
+
71
+ def current
72
+ @current
73
+ end
74
+
75
+ end
@@ -0,0 +1,100 @@
1
+ class FolderContentsHistory
2
+
3
+ require 'json'
4
+
5
+ def initialize(entries = {})
6
+ @entries = entries
7
+ end
8
+
9
+ def load_entries(path = '.')
10
+ entries = Dir["#{path}/*"].inject({}) do |hash, item|
11
+ pathless = item.gsub(/#{path}\//,'')
12
+ hash.update(pathless => FileNameHistory.new(pathless))
13
+ end
14
+ @entries.merge!(entries) do |key, old_hash, new_hash|
15
+ old_hash.history.length > new_hash.history.length ? old_hash : new_hash
16
+ end
17
+ end
18
+
19
+ def plan_renames(regexp, replacement)
20
+ @entries.select { |key, value| key[regexp] }.each_pair do |key, value|
21
+ value.plan_rename(regexp, replacement)
22
+ end
23
+ end
24
+
25
+ def plan_rollbacks
26
+ @entries.each_value do |file_history|
27
+ file_history.plan_rollback
28
+ end
29
+ end
30
+
31
+ def plan_reapplication(path)
32
+ entries = Dir["#{path}/*"].map { |file| file.gsub(/#{path}\//,'') }
33
+ histories = @entries.values
34
+ @entries = entries.inject({}) do |hash, item|
35
+ hash.tap do |obj|
36
+ new_history = histories.select { |history| history.was_named? item }.first
37
+ obj.update(item => new_history.clone) unless new_history.nil?
38
+ end
39
+ end
40
+ @entries.select! { |name, history| history }
41
+ @entries.each { |name, history| history.plan_reapplication(name) }
42
+ end
43
+
44
+ def execute
45
+ changed.each_pair do |key, value|
46
+ value.rename
47
+ @entries[value.current] = @entries.delete(key)
48
+ end
49
+ end
50
+
51
+ def log
52
+ if conflicts?
53
+ puts <<-NOTE.gsub(/^.*\|/, '')
54
+ |==================================================
55
+ |!!! Some files would be lost by this operation !!!
56
+ |==================================================
57
+ NOTE
58
+ end
59
+ changed.each_value do |file_history|
60
+ file_history.log
61
+ end
62
+ end
63
+
64
+ def log_backup(where)
65
+ with_history = @entries.select { |name, history| history.history.length > 1 }
66
+ backup = with_history.inject({}) do |hash, item|
67
+ hash.update(item.last.to_hash)
68
+ end
69
+ File.write(where, JSON.pretty_generate(backup))
70
+ end
71
+
72
+ def to_hash(input_hash = @entries)
73
+ input_hash.inject({}) do |hash, entry|
74
+ hash.update(entry[0] => entry[1].to_hash)
75
+ end
76
+ end
77
+
78
+ def changed
79
+ @entries.select { |_key, value| value.changed? && value.present? }
80
+ end
81
+
82
+ def show_history(files)
83
+ files.each do |file|
84
+ @entries[file] = FileNameHistory.new(file) unless @entries[file]
85
+ @entries[file].print_history
86
+ end
87
+ end
88
+
89
+ def conflicts?
90
+ #changed.map { |entry| entry.last }.compact.uniq.length < changed.length
91
+ @entries.map { |entry| entry.last.last_name }.compact.uniq.length < @entries.length
92
+ end
93
+
94
+ def self.new_from_history(history_file)
95
+ entries = JSON.parse(File.read(history_file)).inject({}) do |hash, item|
96
+ hash.update(item.first => FileNameHistory.new_from_history(item.last))
97
+ end
98
+ FolderContentsHistory.new(entries)
99
+ end
100
+ end
@@ -0,0 +1,41 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest/spec'
3
+ require 'filename_history'
4
+
5
+ describe 'FileNameHistory test' do
6
+ before do
7
+ @filename_history = FileNameHistory.new 'test'
8
+ end
9
+
10
+ it 'has the same current and original name' do
11
+ @filename_history.current.must_equal @filename_history.original
12
+ end
13
+
14
+ it 'has history of length 1' do
15
+ @filename_history.history.must_be_instance_of Array
16
+ @filename_history.history.length.must_equal 1
17
+ end
18
+
19
+ it 'must return a hash' do
20
+ @filename_history.to_hash.must_be_instance_of Hash
21
+ end
22
+
23
+ it 'must not be changed' do
24
+ @filename_history.changed?.must_equal false
25
+ end
26
+
27
+ it 'plans renames correctly' do
28
+ @filename_history.plan_rename(/test/, 'tset')
29
+ @filename_history.history.length.must_equal 2
30
+ @filename_history.changed?.must_equal true
31
+ @filename_history.was_named?('test').must_equal true
32
+ end
33
+
34
+ it 'plans rollbacks correctly' do
35
+ @filename_history.plan_rename(/test/, 'tset')
36
+ @filename_history.changed?.must_equal true
37
+ @filename_history.plan_rollback
38
+ @filename_history.changed?.must_equal false
39
+ @filename_history.was_named?('tset').must_equal true
40
+ end
41
+ end
@@ -0,0 +1,126 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest/spec'
3
+ require 'folder_contents_history'
4
+ require 'tmpdir'
5
+ require 'fileutils'
6
+
7
+ describe 'FolderContentsHistory test' do
8
+
9
+ describe 'when no data is loaded' do
10
+ before do
11
+ @fch = FolderContentsHistory.new
12
+ end
13
+
14
+ it 'is not changed' do
15
+ @fch.changed.must_be_instance_of Hash
16
+ @fch.changed.empty?.must_equal true
17
+ end
18
+
19
+ it 'has no conflicts' do
20
+ @fch.changed.must_be_instance_of Hash
21
+ @fch.changed.length.must_equal 0
22
+ end
23
+
24
+ it 'has empty hash as representation' do
25
+ @fch.to_hash.must_be_instance_of Hash
26
+ @fch.to_hash.empty?.must_equal true
27
+ end
28
+
29
+ it 'returns empty hash when planning' do
30
+ hash = @fch.plan_renames(//,'')
31
+ hash.must_be_instance_of Hash
32
+ hash.empty?.must_equal true
33
+
34
+ hash = @fch.plan_rollbacks
35
+ hash.must_be_instance_of Hash
36
+ hash.empty?.must_equal true
37
+
38
+ hash = @fch.plan_reapplication('.')
39
+ hash.must_be_instance_of Hash
40
+ hash.empty?.must_equal true
41
+ end
42
+
43
+ end
44
+
45
+ describe 'with data loaded' do
46
+ before do
47
+ @path = Dir.mktmpdir('test')
48
+ @old_dir = Dir.pwd
49
+ @fch = FolderContentsHistory.new
50
+ Dir.chdir(@path)
51
+ 5.times do |num|
52
+ File.open("test-#{num}",'w') do |file|
53
+ file.puts("a")
54
+ end
55
+ end
56
+ result = @fch.load_entries('.')
57
+ end
58
+
59
+ it 'loads data properly' do
60
+ result = @fch.load_entries('.')
61
+ result.must_be_instance_of Hash
62
+ result.keys.length.must_equal 5
63
+ end
64
+
65
+ it 'is changed' do
66
+ @fch.plan_renames(/(\d)/,'\1\1')
67
+ @fch.changed.must_be_instance_of Hash
68
+ @fch.changed.empty?.must_equal false
69
+ end
70
+
71
+ it 'has no conflicts' do
72
+ @fch.plan_renames(/(\d)/,'\1\1')
73
+ @fch.conflicts?.must_equal false
74
+ end
75
+
76
+ it 'has hash as representation' do
77
+ @fch.plan_renames(/(\d)/,'\1\1')
78
+ @fch.to_hash.must_be_instance_of Hash
79
+ @fch.to_hash.empty?.must_equal false
80
+ end
81
+
82
+ after do
83
+ Dir.chdir(@old_dir)
84
+ FileUtils.rm_rf(@path)
85
+ end
86
+ end
87
+
88
+ describe 'when reapplying' do
89
+ before do
90
+ @path = Dir.mktmpdir('test')
91
+ @old_dir = Dir.pwd
92
+ Dir.chdir(@path)
93
+ @fch = FolderContentsHistory.new
94
+ File.open("test-1",'w') do |file|
95
+ file.puts("a")
96
+ end
97
+ @fch.load_entries('.')
98
+ @fch.plan_renames(/test-1/,'test-2')
99
+ @fch.execute
100
+ @fch.plan_renames(/test-2/,'test-3')
101
+ @fch.execute
102
+ File.open('test-1','w') do |file|
103
+ file.puts
104
+ end
105
+ end
106
+
107
+ it 'reapplies correctly' do
108
+ File.delete('test-3')
109
+ @fch.plan_reapplication('.')
110
+ @fch.changed.must_be_instance_of Hash
111
+ @fch.changed.empty?.must_equal false
112
+ end
113
+
114
+ it 'warns about conflict' do
115
+ @fch.plan_reapplication('.')
116
+ -> { @fch.log }.must_output(<<-MSG.gsub(/^.*\|/,'')
117
+ |==================================================
118
+ |!!! Some files would be lost by this operation !!!
119
+ |==================================================
120
+ |test-1
121
+ |-> test-3
122
+ MSG
123
+ )
124
+ end
125
+ end
126
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: regren
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Adam Ruzicka
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: json
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ - - ! '>='
35
+ - !ruby/object:Gem::Version
36
+ version: 4.7.3
37
+ type: :development
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: '1.3'
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: 4.7.3
47
+ description: A simple gem for batch renaming files
48
+ email: a.ruzicka@outlook.com
49
+ executables:
50
+ - regren
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - bin/regren
55
+ - lib/filename_history.rb
56
+ - lib/folder_contents_history.rb
57
+ - test/filename_history_test.rb
58
+ - test/folder_contents_history_test.rb
59
+ homepage: https://github.com/adamruzicka/regren
60
+ licenses:
61
+ - MIT
62
+ metadata: {}
63
+ post_install_message:
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ! '>='
70
+ - !ruby/object:Gem::Version
71
+ version: 1.9.2
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubyforge_project:
79
+ rubygems_version: 2.3.0
80
+ signing_key:
81
+ specification_version: 4
82
+ summary: regren
83
+ test_files: []