lib/deblank.rb in deblank-0.0.1 vs lib/deblank.rb in deblank-0.1.0

- old
+ new

@@ -1,6 +1,6 @@ -#!/usr/bin/ruby -w +#!/usr/bin/env ruby # encoding: UTF-8 # == Name # # deblank - remove special characters from filenames # @@ -13,29 +13,31 @@ # # Use <tt>deblank --help</tt> to display a brief help message. # # == Author # -# Copyright (C) 2012 Marcus Stollsteimer +# Copyright (C) 2012-2013 Marcus Stollsteimer # # License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> require 'optparse' # This module contains the classes for the +deblank+ tool. module Deblank PROGNAME = 'deblank' - VERSION = '0.0.1' - DATE = '2012-12-20' + VERSION = '0.1.0' + DATE = '2013-10-27' HOMEPAGE = 'https://github.com/stomar/deblank' TAGLINE = 'remove special characters from filenames' - COPYRIGHT = "Copyright (C) 2012 Marcus Stollsteimer.\n" + - "License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n" + - "This is free software: you are free to change and redistribute it.\n" + - "There is NO WARRANTY, to the extent permitted by law." + COPYRIGHT = <<-copyright.gsub(/^ +/, '') + Copyright (C) 2012-2013 Marcus Stollsteimer. + License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>. + This is free software: you are free to change and redistribute it. + There is NO WARRANTY, to the extent permitted by law. + copyright # Parser for the command line options. # The class method parse! does the job. class Optionparser @@ -48,28 +50,28 @@ # Returns a hash containing the option parameters. def self.parse!(argv) options = { :files => nil, - :simulate => false, + :simulate => false } opt_parser = OptionParser.new do |opt| opt.banner = "Usage: #{PROGNAME} [options] file[s]" - opt.separator '' - opt.separator 'deblank renames files and replaces or removes special characters' - opt.separator 'like spaces, parentheses, or umlauts.' - opt.separator 'The new filename will only contain the following characters:' - opt.separator '' - opt.separator ' ' << NameConverter::VALID_CHARS - opt.separator '' - opt.separator 'Spaces are replaced by underscores, German umlauts and eszett are' - opt.separator 'transliterated, all other invalid characters are removed.' - opt.separator '' - opt.separator 'Options' - opt.separator '' + opt.separator %Q{ + deblank renames files and replaces or removes special characters + like spaces, parentheses, or umlauts. + The new filename will only contain the following characters: + ____#{NameConverter.default_valid_chars_to_s} + + Spaces are replaced by underscores, German umlauts and eszett are + transliterated, all other invalid characters are removed. + + Options + }.gsub(/^ +/, '').gsub(/^____/, ' ') + # process --version and --help first, # exit successfully (GNU Coding Standards) opt.on_tail('-h', '--help', 'Print a brief help message and exit.') do puts opt_parser puts "\nReport bugs on the #{PROGNAME} home page: <#{HOMEPAGE}>" @@ -83,11 +85,11 @@ exit end opt.on('-l', '--list', 'List the used character substitutions.') do - puts NameConverter.substitutions_to_string + puts NameConverter.default_substitutions_to_s exit end opt.on('-n', '--no-act', 'Do not rename files, only display what would happen.') do @@ -122,15 +124,15 @@ string.dup.force_encoding('Windows-1252') end end - # This module provides a converter method for filenames + # This class provides a converter method for filenames # (only the base name is modified). - module NameConverter + class NameConverter - VALID_CHARS = 'A-Z a-z 0-9 . _ -' # spaces are ignored, `-' must be last + VALID_CHARS = 'A-Za-z0-9._-' # `-' must be last SUBSTITUTIONS = { ' ' => '_', 'ä' => 'ae', 'ö' => 'oe', @@ -139,31 +141,41 @@ 'Ö' => 'Oe', 'Ü' => 'Ue', 'ß' => 'ss' } - def self.convert(filename) - invalid_chars = /[^#{VALID_CHARS.tr(' ', '')}]/ - basename = File.basename(filename) - dir = File.dirname(filename) + def initialize + @valid_characters = VALID_CHARS + @substitutions = SUBSTITUTIONS + end - SUBSTITUTIONS.each do |from, to| - basename.gsub!(/#{from}/, to) - end - basename.gsub!(invalid_chars, '') + def convert(filename) + dir, basename = File.dirname(filename), File.basename(filename) + @substitutions.each {|from, to| basename.gsub!(/#{from}/, to) } + basename.gsub!(invalid_characters, '') + dir == '.' ? basename : "#{dir}/#{basename}" end - def self.substitutions_to_string - output = '' - SUBSTITUTIONS.each do |from, to| - output << "#{from} => #{to}\n" - end + def invalid?(filename) + invalid_characters === filename + end - output + def self.default_valid_chars_to_s + VALID_CHARS.scan(/.-.|./).join(' ') end + + def self.default_substitutions_to_s + SUBSTITUTIONS.map {|from, to| "#{from} => #{to}\n" }.join + end + + private + + def invalid_characters + /[^#{@valid_characters}]/ + end end # The main program. It's run! method is called # if the script is run from the command line. # It parses the command line arguments and renames the files. @@ -172,72 +184,84 @@ ERRORCODE = {:general => 1, :usage => 2} def initialize begin options = Optionparser.parse!(ARGV) - @files = options[:files] - @simulate = options[:simulate] rescue => e usage_fail(e.message) end + @files = options[:files] + @simulate = options[:simulate] + @converter = NameConverter.new end # The main program. def run! message = "This is a dry run, files will not be renamed." warn "#{message}\n#{'-' * message.size}\n" if @simulate @files.each do |filename| - unless File.exist?(filename) - warn "There is no file `#{filename}'. (Skipped.)" - next - end + next unless file_exist?(filename) + next unless invalid?(filename) - new_filename = NameConverter.convert(filename) + new_filename = @converter.convert(filename) + secure_rename(filename, new_filename) + end + end - # filenames are identical - if new_filename == filename - warn("`#{filename}' and `#{new_filename}' are the same file. (Skipped.)") - next - end + private - # move file - if File.exist?(new_filename) - overwrite_ok = ask("File `#{new_filename}' already exists. Overwrite?") - next unless overwrite_ok - end + def skip_warn(message) + warn "#{message} (Skipped.)" + end - warn "Moving from `#{filename}' to `#{new_filename}'." - File.rename(filename, new_filename) unless @simulate - end # of each + def file_exist?(filename) + fail_message = "There is no file `#{filename}'." + + File.exist?(filename) or skip_warn(fail_message) end + def invalid?(filename) + fail_message = "`#{filename}' already is a valid filename." + + @converter.invalid?(filename) or skip_warn(fail_message) + end + + def secure_rename(old_filename, new_filename) + return if File.exist?(new_filename) && !overwrite?(new_filename) + + warn "Moving from `#{old_filename}' to `#{new_filename}'." + File.rename(old_filename, new_filename) unless @simulate + end + + def overwrite?(filename) + confirm("File `#{filename}' already exists. Overwrite?") + end + # Asks for yes or no (y/n). # # +question+ - string to be printed # # Returns +true+ if the answer is yes. - def ask(question) # :nodoc: + def confirm(question) loop do $stderr.print "#{question} [y/n] " - reply = $stdin.gets.chomp.downcase # $stdin: avoids gets / ARGV problem - return true if reply == 'y' - return false if reply == 'n' + reply = $stdin.gets.chomp.downcase # $stdin avoids gets/ARGV problem + return reply == 'y' if /\A[yn]\Z/ =~ reply warn "Please answer `y' or `n'." end end # Prints an error message and a short help information, then exits. - def usage_fail(message) # :nodoc: + def usage_fail(message) warn "#{PROGNAME}: #{message}" warn "Use `#{PROGNAME} --help' for valid options." exit ERRORCODE[:usage] end end +end # module ### call main method only if called on command line if __FILE__ == $0 - Application.new.run! + Deblank::Application.new.run! end - -end # module