modname 0.2.4 → 1.0.1
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 +4 -4
- data/lib/modname/banner.rb +1 -0
- data/lib/modname/modder.rb +90 -23
- data/lib/modname/version.rb +1 -1
- data/lib/modname.rb +20 -20
- data/readme.md +4 -5
- metadata +3 -17
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '08c89eeb3f25ef386045b8ee7ac0f52695b24b7491362f18ad0e71dec728a35c'
|
|
4
|
+
data.tar.gz: 442c1038cd4ea91b61e82612468506789fab3d5d9702d97135ec45673be99138
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3e0e66eeb5c5c7bdde75c518f57ec348cbf686af0514e764fd2aeb508d157984b1faf739f3dfb5db55aa72905063c0602ae83557e6dbc35a929dce29bc2524a7
|
|
7
|
+
data.tar.gz: bee28f157f2963a03495a8b44271dc389d4b5923e5ada5be317bd78778e299cd8b66717425d7dbb5623280362bee385d81e3dd018567dfb615b514dc34ec2e96
|
data/lib/modname/banner.rb
CHANGED
|
@@ -11,6 +11,7 @@ Modname::HELP_BANNER = <<~HELP.freeze
|
|
|
11
11
|
-e #{'|'.cyan} change file extensions
|
|
12
12
|
-f #{'|'.cyan} force run; don't pre-check
|
|
13
13
|
-r #{'|'.cyan} run modname recursively
|
|
14
|
+
--dirs #{'|'.cyan} also rename directory names
|
|
14
15
|
-h #{'|'.cyan} show more help, examples
|
|
15
16
|
HELP
|
|
16
17
|
|
data/lib/modname/modder.rb
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
# any module including modder should implement:
|
|
7
7
|
# @transfer => hash of file transfers to occur
|
|
8
|
-
# @options => hash with :recurse and :
|
|
8
|
+
# @options => hash with :recurse, :force, and :dirs
|
|
9
9
|
|
|
10
10
|
# extensions
|
|
11
11
|
module Modder
|
|
@@ -13,10 +13,13 @@ module Modder
|
|
|
13
13
|
def regex(args = [])
|
|
14
14
|
match, trans = Modder.parse args
|
|
15
15
|
|
|
16
|
-
Modder.files(@options[:recurse]).each do |file|
|
|
17
|
-
new =
|
|
16
|
+
Modder.files(@options[:recurse], dirs: @options[:dirs]).each do |file|
|
|
17
|
+
new = Modder.rename_base(file) do |base|
|
|
18
|
+
result = base.sub Regexp.new(match), trans
|
|
19
|
+
result.empty? ? base : result # no changes
|
|
20
|
+
end
|
|
18
21
|
|
|
19
|
-
next if new
|
|
22
|
+
next if new.nil?
|
|
20
23
|
|
|
21
24
|
@transfer[file] = new
|
|
22
25
|
end
|
|
@@ -35,10 +38,10 @@ module Modder
|
|
|
35
38
|
undercase_ext match
|
|
36
39
|
|
|
37
40
|
else # move match extension to targeted
|
|
38
|
-
Modder.files(@options[:recurse]).each do |file|
|
|
39
|
-
new = file.sub(/#{match}$/, trans)
|
|
41
|
+
Modder.files(@options[:recurse], dirs: @options[:dirs]).each do |file|
|
|
42
|
+
new = Modder.rename_base(file) { |base| base.sub(/#{match}$/, trans) }
|
|
40
43
|
|
|
41
|
-
next if new
|
|
44
|
+
next if new.nil?
|
|
42
45
|
|
|
43
46
|
@transfer[file] = new
|
|
44
47
|
end
|
|
@@ -49,7 +52,7 @@ module Modder
|
|
|
49
52
|
|
|
50
53
|
# top level wrapper for exts
|
|
51
54
|
def undercase_ext(ext = '')
|
|
52
|
-
transfer = Modder.undercase_ext_get ext, @options[:recurse]
|
|
55
|
+
transfer = Modder.undercase_ext_get ext, @options[:recurse], dirs: @options[:dirs]
|
|
53
56
|
Modder.undercase_ext_set ext, transfer, @options[:force]
|
|
54
57
|
end
|
|
55
58
|
end
|
|
@@ -71,15 +74,52 @@ class << Modder
|
|
|
71
74
|
$stdin.gets.chomp.downcase[0] == 'y'
|
|
72
75
|
end
|
|
73
76
|
|
|
74
|
-
# return a list of files to examine
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
# return a list of files (and, optionally, directories) to examine.
|
|
78
|
+
# files always come first; directories are sorted deepest-first so a
|
|
79
|
+
# parent is only renamed after everything nested inside it.
|
|
80
|
+
# dotfiles/dotdirs (.git, etc.) are always excluded, matching Dir['**/*']'s
|
|
81
|
+
# default behavior in the recursive case
|
|
82
|
+
def files(recurse, dirs: false)
|
|
83
|
+
paths = recurse ? Dir['**/*'] : Dir.entries(Dir.pwd).reject { |f| f.start_with?('.') }
|
|
84
|
+
found, folders = Modder.classify(paths)
|
|
85
|
+
|
|
86
|
+
return found unless dirs
|
|
87
|
+
|
|
88
|
+
found + folders.sort_by { |f| -f.count('/') }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# split paths into [files, directories]; a single stat covers both checks
|
|
92
|
+
# per path, skipping entries that vanished mid-scan or are broken symlinks
|
|
93
|
+
def classify(paths)
|
|
94
|
+
paths.each_with_object([[], []]) do |f, (fs, ds)|
|
|
95
|
+
stat = Modder.safe_stat(f)
|
|
96
|
+
next unless stat
|
|
97
|
+
|
|
98
|
+
fs << f if stat.file?
|
|
99
|
+
ds << f if stat.directory?
|
|
80
100
|
end
|
|
81
101
|
end
|
|
82
102
|
|
|
103
|
+
# stat a path, returning nil (instead of raising) for entries that
|
|
104
|
+
# disappeared mid-scan or are broken symlinks
|
|
105
|
+
def safe_stat(file)
|
|
106
|
+
File.stat(file)
|
|
107
|
+
rescue Errno::ENOENT
|
|
108
|
+
nil
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# apply a transformation to a file's basename only, preserving its directory.
|
|
112
|
+
# the block receives the basename and returns a new basename; returning it
|
|
113
|
+
# unchanged means no rename should happen, in which case nil is returned
|
|
114
|
+
def rename_base(file)
|
|
115
|
+
dir, base = File.split(file)
|
|
116
|
+
new_base = yield base
|
|
117
|
+
|
|
118
|
+
return nil if new_base == base
|
|
119
|
+
|
|
120
|
+
dir == '.' ? new_base : File.join(dir, new_base)
|
|
121
|
+
end
|
|
122
|
+
|
|
83
123
|
# show the status of current files
|
|
84
124
|
def status(transfer)
|
|
85
125
|
if transfer.empty?
|
|
@@ -90,7 +130,9 @@ class << Modder
|
|
|
90
130
|
end
|
|
91
131
|
end
|
|
92
132
|
|
|
93
|
-
# rename all files
|
|
133
|
+
# rename all files. must run in insertion order: when directories are
|
|
134
|
+
# included, Modder.files places them deepest-first so a parent is never
|
|
135
|
+
# renamed before what's nested inside it
|
|
94
136
|
def execute(transfer, force: false)
|
|
95
137
|
transfer.each { |o, n| Modder.rename o, n, force }
|
|
96
138
|
end
|
|
@@ -123,19 +165,40 @@ class << Modder
|
|
|
123
165
|
end
|
|
124
166
|
|
|
125
167
|
# get all extensions to change
|
|
126
|
-
def undercase_ext_get(ext, recurse)
|
|
168
|
+
def undercase_ext_get(ext, recurse, dirs: false)
|
|
127
169
|
transfer = {}
|
|
128
170
|
allexts = ext.empty?
|
|
129
|
-
Modder.files(recurse).each do |file|
|
|
130
|
-
|
|
171
|
+
Modder.files(recurse, dirs: dirs).each do |file|
|
|
172
|
+
new = Modder.rename_base(file) do |base|
|
|
173
|
+
cur_ext = allexts ? base.split('.').last : ext
|
|
174
|
+
next base if cur_ext == base # no extension to change
|
|
175
|
+
|
|
176
|
+
base.sub(/#{cur_ext}$/i, cur_ext.downcase)
|
|
177
|
+
end
|
|
131
178
|
|
|
132
|
-
|
|
133
|
-
next if new == file || ext == file # no changes or extension
|
|
179
|
+
next if new.nil?
|
|
134
180
|
|
|
135
181
|
transfer[file] = new
|
|
136
182
|
end
|
|
137
183
|
|
|
138
|
-
transfer
|
|
184
|
+
Modder.resolve_dir_renames(transfer)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# rewrite each target path so it reflects any ancestor directory renames
|
|
188
|
+
# also present in this transfer. undercase_ext_set flattens every entry to
|
|
189
|
+
# a bare temp name before restoring it, which loses the "renaming a
|
|
190
|
+
# directory carries its contents along" guarantee that Modder.execute
|
|
191
|
+
# otherwise gets for free from File.rename — so a nested file's precomputed
|
|
192
|
+
# target must have its renamed ancestor directory's NEW name spliced in
|
|
193
|
+
def resolve_dir_renames(transfer)
|
|
194
|
+
dir_renames = transfer.select { |k, _| File.directory?(k) }
|
|
195
|
+
return transfer if dir_renames.empty?
|
|
196
|
+
|
|
197
|
+
transfer.transform_values do |target|
|
|
198
|
+
dir_renames.reduce(target) do |path, (old_dir, new_dir)|
|
|
199
|
+
path.start_with?("#{old_dir}/") ? path.sub("#{old_dir}/", "#{new_dir}/") : path
|
|
200
|
+
end
|
|
201
|
+
end
|
|
139
202
|
end
|
|
140
203
|
|
|
141
204
|
# set all extensions to change
|
|
@@ -153,7 +216,8 @@ class << Modder
|
|
|
153
216
|
final = {}
|
|
154
217
|
temp = {}
|
|
155
218
|
|
|
156
|
-
# create hash temp map
|
|
219
|
+
# create hash temp map; iterating `transfer` preserves its deepest-first
|
|
220
|
+
# directory ordering, which `temp` needs to safely rename originals away
|
|
157
221
|
transfer.each do |k, v|
|
|
158
222
|
tempfile = (v.hash * v.object_id).abs.to_s
|
|
159
223
|
final[tempfile] = v
|
|
@@ -161,7 +225,10 @@ class << Modder
|
|
|
161
225
|
end
|
|
162
226
|
|
|
163
227
|
Modder.execute temp
|
|
164
|
-
Modder.
|
|
228
|
+
# `transfer`'s order is files-then-dirs-deepest-first (see Modder.files);
|
|
229
|
+
# reversing it gives dirs-shallowest-first-then-files, which is exactly
|
|
230
|
+
# what's needed to restore ancestors before anything nested inside them
|
|
231
|
+
Modder.execute(final.to_a.reverse)
|
|
165
232
|
puts 'Modifications complete.'
|
|
166
233
|
else
|
|
167
234
|
puts 'No modifications done.'
|
data/lib/modname/version.rb
CHANGED
data/lib/modname.rb
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
# parse modname's command line args
|
|
4
4
|
|
|
5
5
|
require 'colored'
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
require_relative 'modname/banner'
|
|
7
|
+
require_relative 'modname/modder'
|
|
8
|
+
require_relative 'modname/version'
|
|
9
9
|
|
|
10
10
|
# Modname is a versatile file naming tool for renaming groups of files
|
|
11
11
|
module Modname
|
|
@@ -13,10 +13,7 @@ module Modname
|
|
|
13
13
|
class << self
|
|
14
14
|
def run(args) = Driver.new.run(args)
|
|
15
15
|
end
|
|
16
|
-
end
|
|
17
16
|
|
|
18
|
-
# Modname module containing the CLI driver
|
|
19
|
-
module Modname
|
|
20
17
|
# Driver class handles command-line argument parsing and execution
|
|
21
18
|
class Driver
|
|
22
19
|
include Modder
|
|
@@ -24,7 +21,7 @@ module Modname
|
|
|
24
21
|
attr_reader :options
|
|
25
22
|
|
|
26
23
|
def initialize
|
|
27
|
-
@options = { force: false, recurse: false }
|
|
24
|
+
@options = { force: false, recurse: false, dirs: false }
|
|
28
25
|
@transfer = {}
|
|
29
26
|
end
|
|
30
27
|
|
|
@@ -52,24 +49,27 @@ module Modname
|
|
|
52
49
|
end
|
|
53
50
|
end
|
|
54
51
|
|
|
52
|
+
# boolean options toggled by a single flag with no argument
|
|
53
|
+
FLAGS = { '-f' => :force, '-r' => :recurse, '--dirs' => :dirs }.freeze
|
|
54
|
+
|
|
55
|
+
# flags that select which command to run
|
|
56
|
+
COMMANDS = {
|
|
57
|
+
'-e' => 'ext', '--ext' => 'ext',
|
|
58
|
+
'-h' => 'help', '--help' => 'help',
|
|
59
|
+
'-v' => 'version', '--version' => 'version'
|
|
60
|
+
}.freeze
|
|
61
|
+
|
|
55
62
|
# parse out arguments
|
|
56
63
|
def parse(args)
|
|
57
64
|
opts = { cmd: 'file', args: [] }
|
|
58
65
|
|
|
59
66
|
args.each do |opt|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
opts[:cmd] = 'ext'
|
|
67
|
-
when '-h', '--help'
|
|
68
|
-
opts[:cmd] = 'help'
|
|
69
|
-
when '-v', '--version'
|
|
70
|
-
opts[:cmd] = 'version'
|
|
71
|
-
else # command argument
|
|
72
|
-
opts[:args] << opt
|
|
67
|
+
if (flag = FLAGS[opt])
|
|
68
|
+
@options[flag] = true
|
|
69
|
+
elsif (cmd = COMMANDS[opt])
|
|
70
|
+
opts[:cmd] = cmd
|
|
71
|
+
else
|
|
72
|
+
opts[:args] << opt # command argument
|
|
73
73
|
end
|
|
74
74
|
end
|
|
75
75
|
|
data/readme.md
CHANGED
|
@@ -28,6 +28,7 @@ modname | rename files, fast
|
|
|
28
28
|
-e | change file extensions
|
|
29
29
|
-f | force run; don't pre-check
|
|
30
30
|
-r | run modname recursively
|
|
31
|
+
--dirs | also rename directory names
|
|
31
32
|
-h | show more help, examples
|
|
32
33
|
|
|
33
34
|
commands
|
|
@@ -70,16 +71,14 @@ try running `gem install bundler`. Once that is done:
|
|
|
70
71
|
Now, we should be able to build the gem locally. This will build the local
|
|
71
72
|
modname gem and link it in your path, so you can playing around with `modname`.
|
|
72
73
|
|
|
73
|
-
|
|
74
|
+
just build
|
|
74
75
|
|
|
75
|
-
This uses `rspec` and `
|
|
76
|
+
This uses `rspec` and `just` to run a suite of unit tests. To run the suite:
|
|
76
77
|
|
|
77
|
-
|
|
78
|
+
just spec
|
|
78
79
|
|
|
79
80
|
|
|
80
81
|
## todos
|
|
81
82
|
|
|
82
|
-
- only try to rename filenames, not folders on recursive calls
|
|
83
83
|
- figure out whats up with the extension globbing
|
|
84
|
-
- apply optionally to directories as well
|
|
85
84
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: modname
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jeremy Warner
|
|
@@ -23,20 +23,6 @@ dependencies:
|
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '1.2'
|
|
26
|
-
- !ruby/object:Gem::Dependency
|
|
27
|
-
name: rake
|
|
28
|
-
requirement: !ruby/object:Gem::Requirement
|
|
29
|
-
requirements:
|
|
30
|
-
- - ">="
|
|
31
|
-
- !ruby/object:Gem::Version
|
|
32
|
-
version: '0'
|
|
33
|
-
type: :development
|
|
34
|
-
prerelease: false
|
|
35
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
-
requirements:
|
|
37
|
-
- - ">="
|
|
38
|
-
- !ruby/object:Gem::Version
|
|
39
|
-
version: '0'
|
|
40
26
|
- !ruby/object:Gem::Dependency
|
|
41
27
|
name: rspec
|
|
42
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -103,14 +89,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
103
89
|
requirements:
|
|
104
90
|
- - ">="
|
|
105
91
|
- !ruby/object:Gem::Version
|
|
106
|
-
version: '0'
|
|
92
|
+
version: '3.0'
|
|
107
93
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
108
94
|
requirements:
|
|
109
95
|
- - ">="
|
|
110
96
|
- !ruby/object:Gem::Version
|
|
111
97
|
version: '0'
|
|
112
98
|
requirements: []
|
|
113
|
-
rubygems_version:
|
|
99
|
+
rubygems_version: 4.0.10
|
|
114
100
|
specification_version: 4
|
|
115
101
|
summary: a versatile file naming tool.
|
|
116
102
|
test_files: []
|