modname 1.0.0 → 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 +64 -16
- data/lib/modname/version.rb +1 -1
- data/lib/modname.rb +17 -14
- data/readme.md +1 -1
- metadata +2 -2
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,7 +13,7 @@ module Modder
|
|
|
13
13
|
def regex(args = [])
|
|
14
14
|
match, trans = Modder.parse args
|
|
15
15
|
|
|
16
|
-
Modder.files(@options[:recurse]).each do |file|
|
|
16
|
+
Modder.files(@options[:recurse], dirs: @options[:dirs]).each do |file|
|
|
17
17
|
new = Modder.rename_base(file) do |base|
|
|
18
18
|
result = base.sub Regexp.new(match), trans
|
|
19
19
|
result.empty? ? base : result # no changes
|
|
@@ -38,7 +38,7 @@ module Modder
|
|
|
38
38
|
undercase_ext match
|
|
39
39
|
|
|
40
40
|
else # move match extension to targeted
|
|
41
|
-
Modder.files(@options[:recurse]).each do |file|
|
|
41
|
+
Modder.files(@options[:recurse], dirs: @options[:dirs]).each do |file|
|
|
42
42
|
new = Modder.rename_base(file) { |base| base.sub(/#{match}$/, trans) }
|
|
43
43
|
|
|
44
44
|
next if new.nil?
|
|
@@ -52,7 +52,7 @@ module Modder
|
|
|
52
52
|
|
|
53
53
|
# top level wrapper for exts
|
|
54
54
|
def undercase_ext(ext = '')
|
|
55
|
-
transfer = Modder.undercase_ext_get ext, @options[:recurse]
|
|
55
|
+
transfer = Modder.undercase_ext_get ext, @options[:recurse], dirs: @options[:dirs]
|
|
56
56
|
Modder.undercase_ext_set ext, transfer, @options[:force]
|
|
57
57
|
end
|
|
58
58
|
end
|
|
@@ -74,15 +74,40 @@ class << Modder
|
|
|
74
74
|
$stdin.gets.chomp.downcase[0] == 'y'
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
-
# return a list of files to examine
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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?
|
|
83
100
|
end
|
|
84
101
|
end
|
|
85
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
|
+
|
|
86
111
|
# apply a transformation to a file's basename only, preserving its directory.
|
|
87
112
|
# the block receives the basename and returns a new basename; returning it
|
|
88
113
|
# unchanged means no rename should happen, in which case nil is returned
|
|
@@ -105,7 +130,9 @@ class << Modder
|
|
|
105
130
|
end
|
|
106
131
|
end
|
|
107
132
|
|
|
108
|
-
# 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
|
|
109
136
|
def execute(transfer, force: false)
|
|
110
137
|
transfer.each { |o, n| Modder.rename o, n, force }
|
|
111
138
|
end
|
|
@@ -138,10 +165,10 @@ class << Modder
|
|
|
138
165
|
end
|
|
139
166
|
|
|
140
167
|
# get all extensions to change
|
|
141
|
-
def undercase_ext_get(ext, recurse)
|
|
168
|
+
def undercase_ext_get(ext, recurse, dirs: false)
|
|
142
169
|
transfer = {}
|
|
143
170
|
allexts = ext.empty?
|
|
144
|
-
Modder.files(recurse).each do |file|
|
|
171
|
+
Modder.files(recurse, dirs: dirs).each do |file|
|
|
145
172
|
new = Modder.rename_base(file) do |base|
|
|
146
173
|
cur_ext = allexts ? base.split('.').last : ext
|
|
147
174
|
next base if cur_ext == base # no extension to change
|
|
@@ -154,7 +181,24 @@ class << Modder
|
|
|
154
181
|
transfer[file] = new
|
|
155
182
|
end
|
|
156
183
|
|
|
157
|
-
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
|
|
158
202
|
end
|
|
159
203
|
|
|
160
204
|
# set all extensions to change
|
|
@@ -172,7 +216,8 @@ class << Modder
|
|
|
172
216
|
final = {}
|
|
173
217
|
temp = {}
|
|
174
218
|
|
|
175
|
-
# 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
|
|
176
221
|
transfer.each do |k, v|
|
|
177
222
|
tempfile = (v.hash * v.object_id).abs.to_s
|
|
178
223
|
final[tempfile] = v
|
|
@@ -180,7 +225,10 @@ class << Modder
|
|
|
180
225
|
end
|
|
181
226
|
|
|
182
227
|
Modder.execute temp
|
|
183
|
-
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)
|
|
184
232
|
puts 'Modifications complete.'
|
|
185
233
|
else
|
|
186
234
|
puts 'No modifications done.'
|
data/lib/modname/version.rb
CHANGED
data/lib/modname.rb
CHANGED
|
@@ -21,7 +21,7 @@ module Modname
|
|
|
21
21
|
attr_reader :options
|
|
22
22
|
|
|
23
23
|
def initialize
|
|
24
|
-
@options = { force: false, recurse: false }
|
|
24
|
+
@options = { force: false, recurse: false, dirs: false }
|
|
25
25
|
@transfer = {}
|
|
26
26
|
end
|
|
27
27
|
|
|
@@ -49,24 +49,27 @@ module Modname
|
|
|
49
49
|
end
|
|
50
50
|
end
|
|
51
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
|
+
|
|
52
62
|
# parse out arguments
|
|
53
63
|
def parse(args)
|
|
54
64
|
opts = { cmd: 'file', args: [] }
|
|
55
65
|
|
|
56
66
|
args.each do |opt|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
opts[:cmd] = 'ext'
|
|
64
|
-
when '-h', '--help'
|
|
65
|
-
opts[:cmd] = 'help'
|
|
66
|
-
when '-v', '--version'
|
|
67
|
-
opts[:cmd] = 'version'
|
|
68
|
-
else # command argument
|
|
69
|
-
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
|
|
70
73
|
end
|
|
71
74
|
end
|
|
72
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
|
|
@@ -80,5 +81,4 @@ This uses `rspec` and `just` to run a suite of unit tests. To run the suite:
|
|
|
80
81
|
## todos
|
|
81
82
|
|
|
82
83
|
- figure out whats up with the extension globbing
|
|
83
|
-
- apply optionally to directories as well
|
|
84
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: 1.0.
|
|
4
|
+
version: 1.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jeremy Warner
|
|
@@ -89,7 +89,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
89
89
|
requirements:
|
|
90
90
|
- - ">="
|
|
91
91
|
- !ruby/object:Gem::Version
|
|
92
|
-
version: '0'
|
|
92
|
+
version: '3.0'
|
|
93
93
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
94
94
|
requirements:
|
|
95
95
|
- - ">="
|