class Fned::FilenameEdit

Public Class Methods

main(*args) click to toggle source
# File lib/fned/filename_edit.rb, line 25
def self.main(*args)
  # Filenames may be in any encoding, i.e. have an invalid encoding if
  # the default encoding is not binary.  Therefore ensure that every
  # argument is in binary encoding.
  args = args.map { |str| str.dup.force_encoding "binary" }

  options = {}

  option_parser = OptionParser.new do |parser|
    parser.version = VERSION

    parser.banner = "Usage: #{File.basename($0)} [options] <files..>"

    parser.on("-v", "--verbose", "verbose output") do |v|
      options[:verbose] = true
    end

    parser.on("-r", "--recursive", "rename files in all subdirectories") do |v|
      options[:recursive] = true
    end

    parser.on("-sSEPARATOR", "--separator=SEPARATOR", "separator between line number and filename") do |v|
      options[:separator] = v
    end
  end

  begin
    option_parser.parse!(args)
  rescue OptionParser::ParseError
    warn $!.message
    return false
  end

  if args.empty?
    $stderr.puts option_parser.help
    return false
  end

  self.new(args, options).run
end
new(paths, options = {}) click to toggle source
# File lib/fned/filename_edit.rb, line 66
def initialize(paths, options = {})
  @paths = paths.map { |path| Pathname.new(path) }
  @options = {
    :verbose => false,
    :recursive => false,
  }.merge(options)

  if @options[:recursive]
    @paths = @paths.map { |path| [path] + walk(path) }.flatten
  end

  # Ensure all paths are in binary encoding, otherwise comparison
  # wont work after reading the files back from the file.
  @paths.map! { |path| Pathname.new(path.to_s.force_encoding("binary")) }
  # Don't display the same path multiple times.
  @paths.uniq!
  # It is not possible to rename `.' and `..'.
  @paths.reject! { |path| %w(. ..).include?(path.basename.to_s) }

  @errors = []
end

Public Instance Methods

edit() click to toggle source
# File lib/fned/filename_edit.rb, line 123
def edit
  # TODO: option to edit the full path or relative path instead of
  # basename only
  # TODO: hide filename extension
  items = @paths.map(&:basename).map(&:to_s)

  # add path before each block of files in the same directory
  comments = []
  dir = nil
  @paths.each.with_index do |path, index|
    next if path.dirname == dir
    comments[index] = "#{path.dirname}/"
    dir = path.dirname
  end

  items_new = EditList.new(@options).edit(items, comments)

  items_new.map.with_index do |path, index|
    # TODO: Pathname#dirname + path breaks if path contains ..
    # because .. is resolved by removing one component which may
    # change the directory if symlinks are in the path.
    @paths[index].dirname + path if path
  end
end
run() click to toggle source
# File lib/fned/filename_edit.rb, line 148
def run
  rename = Rename.new(@options)
  @paths.zip(edit).each do |source, destination|
    # TODO: option to delete files (interatively?) that are dropped
    # from the list
    next unless destination
    rename.add(source, destination)
  end
  rename.rename_files.empty?
end
walk(path) click to toggle source

Recursively walk through path and return path of all entries. Does not include path itself.

# File lib/fned/filename_edit.rb, line 90
def walk(path)
  # TODO: descend into symlinked directories?
  # TODO: handle errors (ENOENT, ENOTDIR, ELOOP, EACCESS)
  return [path] unless path.lstat.directory?

  entries = path.entries
  .map do |entry|
    entry.to_s.force_encoding "binary"
  end
  .reject do |entry|
    %w(. ..).include?(entry)
  end
  .map do |entry|
    if (path + entry).lstat.directory?
      path + "#{entry}/"
    else
      path + entry
    end
  end
  .sort_by do |entry|
    [entry.lstat.directory? ? 0 : 1, entry.basename]
  end

  result = []
  result += entries
  result += entries.map do |entry|
    if entry.lstat.directory?
      walk(entry)
    end
  end.compact.flatten
  result
end