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