regren 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.
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: []