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 +7 -0
- data/bin/regren +94 -0
- data/lib/filename_history.rb +75 -0
- data/lib/folder_contents_history.rb +100 -0
- data/test/filename_history_test.rb +41 -0
- data/test/folder_contents_history_test.rb +126 -0
- metadata +83 -0
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: []
|