Module: Modder
- Included in:
- Modname::Driver
- Defined in:
- lib/modname/modder.rb
Overview
extensions
Class Method Summary collapse
-
.classify(paths) ⇒ Object
split paths into [files, directories]; a single stat covers both checks per path, skipping entries that vanished mid-scan or are broken symlinks.
-
.confirm? ⇒ Boolean
double check transformations.
-
.execute(transfer, force: false) ⇒ Object
rename all files.
-
.files(recurse, dirs: false) ⇒ Object
return a list of files (and, optionally, directories) to examine.
-
.finish(transfer, force: false) ⇒ Object
finish up execution, highest level wrapper.
-
.parse(args) ⇒ Object
return appropriate args, repairing if undefined.
-
.rename(old, new, force) ⇒ Object
try to rename a given file.
-
.rename_base(file) ⇒ Object
apply a transformation to a file's basename only, preserving its directory.
-
.resolve_dir_renames(transfer) ⇒ Object
rewrite each target path so it reflects any ancestor directory renames also present in this transfer.
-
.safe_stat(file) ⇒ Object
stat a path, returning nil (instead of raising) for entries that disappeared mid-scan or are broken symlinks.
-
.status(transfer) ⇒ Object
show the status of current files.
-
.undercase_ext_get(ext, recurse, dirs: false) ⇒ Object
get all extensions to change.
-
.undercase_ext_set(ext, transfer, force) ⇒ Object
set all extensions to change this involves moving it to a tmp file first, since most file systems are not case sensitive and therefore wont distiniguish between HI and hi.
Instance Method Summary collapse
-
#exts(args = []) ⇒ Object
change one file extension to another's type.
-
#regex(args = []) ⇒ Object
rename files based on regular expressions.
-
#undercase_ext(ext = '') ⇒ Object
top level wrapper for exts.
Class Method Details
.classify(paths) ⇒ Object
split paths into [files, directories]; a single stat covers both checks per path, skipping entries that vanished mid-scan or are broken symlinks
93 94 95 96 97 98 99 100 101 |
# File 'lib/modname/modder.rb', line 93 def classify(paths) paths.each_with_object([[], []]) do |f, (fs, ds)| stat = Modder.safe_stat(f) next unless stat fs << f if stat.file? ds << f if stat.directory? end end |
.confirm? ⇒ Boolean
double check transformations
72 73 74 75 |
# File 'lib/modname/modder.rb', line 72 def confirm? print 'Are these changes ok? [yN] ' $stdin.gets.chomp.downcase[0] == 'y' end |
.execute(transfer, force: false) ⇒ Object
rename all files. must run in insertion order: when directories are included, Modder.files places them deepest-first so a parent is never renamed before what's nested inside it
136 137 138 |
# File 'lib/modname/modder.rb', line 136 def execute(transfer, force: false) transfer.each { |o, n| Modder.rename o, n, force } end |
.files(recurse, dirs: false) ⇒ Object
return a list of files (and, optionally, directories) to examine. files always come first; directories are sorted deepest-first so a parent is only renamed after everything nested inside it. dotfiles/dotdirs (.git, etc.) are always excluded, matching Dir's default behavior in the recursive case
82 83 84 85 86 87 88 89 |
# File 'lib/modname/modder.rb', line 82 def files(recurse, dirs: false) paths = recurse ? Dir['**/*'] : Dir.entries(Dir.pwd).reject { |f| f.start_with?('.') } found, folders = Modder.classify(paths) return found unless dirs found + folders.sort_by { |f| -f.count('/') } end |
.finish(transfer, force: false) ⇒ Object
finish up execution, highest level wrapper
141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/modname/modder.rb', line 141 def finish(transfer, force: false) # print changes, return if none Modder.status transfer return if transfer.empty? if force || Modder.confirm? Modder.execute transfer, force: force puts 'Modifications complete.' else puts 'No modifications done.' end end |
.parse(args) ⇒ Object
return appropriate args, repairing if undefined
63 64 65 66 67 68 69 |
# File 'lib/modname/modder.rb', line 63 def parse(args) match = args.shift trans = args.shift match = '' if match.nil? trans = '' if trans.nil? [match, trans] end |
.rename(old, new, force) ⇒ Object
try to rename a given file
155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/modname/modder.rb', line 155 def rename(old, new, force) exist = "#{'Error:'.red} target file |#{new.green}| already exists" # only overwrite when forced raise(exist) if (!force) && File.exist?(new) File.rename(old, new) rescue StandardError => e puts "#{'Error:'.red} could not move |#{old.red}| to |#{new.green}|" puts e. end |
.rename_base(file) ⇒ Object
apply a transformation to a file's basename only, preserving its directory. the block receives the basename and returns a new basename; returning it unchanged means no rename should happen, in which case nil is returned
114 115 116 117 118 119 120 121 |
# File 'lib/modname/modder.rb', line 114 def rename_base(file) dir, base = File.split(file) new_base = yield base return nil if new_base == base dir == '.' ? new_base : File.join(dir, new_base) end |
.resolve_dir_renames(transfer) ⇒ Object
rewrite each target path so it reflects any ancestor directory renames also present in this transfer. undercase_ext_set flattens every entry to a bare temp name before restoring it, which loses the "renaming a directory carries its contents along" guarantee that Modder.execute otherwise gets for free from File.rename — so a nested file's precomputed target must have its renamed ancestor directory's NEW name spliced in
193 194 195 196 197 198 199 200 201 202 |
# File 'lib/modname/modder.rb', line 193 def resolve_dir_renames(transfer) dir_renames = transfer.select { |k, _| File.directory?(k) } return transfer if dir_renames.empty? transfer.transform_values do |target| dir_renames.reduce(target) do |path, (old_dir, new_dir)| path.start_with?("#{old_dir}/") ? path.sub("#{old_dir}/", "#{new_dir}/") : path end end end |
.safe_stat(file) ⇒ Object
stat a path, returning nil (instead of raising) for entries that disappeared mid-scan or are broken symlinks
105 106 107 108 109 |
# File 'lib/modname/modder.rb', line 105 def safe_stat(file) File.stat(file) rescue Errno::ENOENT nil end |
.status(transfer) ⇒ Object
show the status of current files
124 125 126 127 128 129 130 131 |
# File 'lib/modname/modder.rb', line 124 def status(transfer) if transfer.empty? puts 'No matches found.'.yellow else puts 'Planned file actions:'.green transfer.each { |o, n| puts "\t#{o} -> #{n.green}" } end end |
.undercase_ext_get(ext, recurse, dirs: false) ⇒ Object
get all extensions to change
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/modname/modder.rb', line 168 def undercase_ext_get(ext, recurse, dirs: false) transfer = {} allexts = ext.empty? Modder.files(recurse, dirs: dirs).each do |file| new = Modder.rename_base(file) do |base| cur_ext = allexts ? base.split('.').last : ext next base if cur_ext == base # no extension to change base.sub(/#{cur_ext}$/i, cur_ext.downcase) end next if new.nil? transfer[file] = new end Modder.resolve_dir_renames(transfer) end |
.undercase_ext_set(ext, transfer, force) ⇒ Object
set all extensions to change this involves moving it to a tmp file first, since most file systems are not case sensitive and therefore wont distiniguish between HI and hi. to get around this, we can set HI to HI.hash, then set HI.hash to hi
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/modname/modder.rb', line 208 def undercase_ext_set(ext, transfer, force) puts 'Lowering extension: '.green + (ext.empty? ? '*' : ext) Modder.status transfer return if transfer.empty? # confirm current changes if force || Modder.confirm? final = {} temp = {} # create hash temp map; iterating `transfer` preserves its deepest-first # directory ordering, which `temp` needs to safely rename originals away transfer.each do |k, v| tempfile = (v.hash * v.object_id).abs.to_s final[tempfile] = v temp[k] = tempfile end Modder.execute temp # `transfer`'s order is files-then-dirs-deepest-first (see Modder.files); # reversing it gives dirs-shallowest-first-then-files, which is exactly # what's needed to restore ancestors before anything nested inside them Modder.execute(final.to_a.reverse) puts 'Modifications complete.' else puts 'No modifications done.' end end |
Instance Method Details
#exts(args = []) ⇒ Object
change one file extension to another's type
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/modname/modder.rb', line 31 def exts(args = []) match, trans = Modder.parse args if match.empty? && trans.empty? # do all undercase_ext elsif trans.empty? # undercase one ext undercase_ext match else # move match extension to targeted Modder.files(@options[:recurse], dirs: @options[:dirs]).each do |file| new = Modder.rename_base(file) { |base| base.sub(/#{match}$/, trans) } next if new.nil? @transfer[file] = new end Modder.finish @transfer, force: @options[:force] end end |
#regex(args = []) ⇒ Object
rename files based on regular expressions
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
# File 'lib/modname/modder.rb', line 13 def regex(args = []) match, trans = Modder.parse args Modder.files(@options[:recurse], dirs: @options[:dirs]).each do |file| new = Modder.rename_base(file) do |base| result = base.sub Regexp.new(match), trans result.empty? ? base : result # no changes end next if new.nil? @transfer[file] = new end Modder.finish @transfer, force: @options[:force] end |
#undercase_ext(ext = '') ⇒ Object
top level wrapper for exts
54 55 56 57 |
# File 'lib/modname/modder.rb', line 54 def undercase_ext(ext = '') transfer = Modder.undercase_ext_get ext, @options[:recurse], dirs: @options[:dirs] Modder.undercase_ext_set ext, transfer, @options[:force] end |