fixnames 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,92 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{fixnames}
8
+ s.version = "0.3.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = [%q{Brent Sanders}]
12
+ s.date = %q{2011-10-04}
13
+ s.description = %q{Cleans up filenames so they can easily be used
14
+ in scripts, without annoyances such as spaces or other bad characters}
15
+ s.email = %q{git@thoughtnoise.net}
16
+ s.executables = [%q{fixnames}, %q{fixdirs}]
17
+ s.extra_rdoc_files = [
18
+ "LICENSE.txt",
19
+ "README.md",
20
+ "README.rdoc"
21
+ ]
22
+ s.files = [
23
+ ".document",
24
+ ".rspec",
25
+ "COPYING",
26
+ "LICENSE.txt",
27
+ "README.md",
28
+ "README.rdoc",
29
+ "Rakefile",
30
+ "VERSION",
31
+ "bin/fixdirs",
32
+ "bin/fixnames",
33
+ "fixnames.gemspec",
34
+ "lib/fixnames.rb",
35
+ "lib/fixnames/debug.rb",
36
+ "lib/fixnames/engine.rb",
37
+ "lib/fixnames/engine/scan_dir.rb",
38
+ "lib/fixnames/filters.rb",
39
+ "lib/fixnames/helpers.rb",
40
+ "lib/fixnames/interface.rb",
41
+ "lib/fixnames/option.rb",
42
+ "lib/fixnames/version.rb",
43
+ "spec/fixnames/banners_spec.rb",
44
+ "spec/fixnames/brackets_spec.rb",
45
+ "spec/fixnames/camelcase_spec.rb",
46
+ "spec/fixnames/charstrip_spec.rb",
47
+ "spec/fixnames/checksums_spec.rb",
48
+ "spec/fixnames/fixdots_spec.rb",
49
+ "spec/fixnames/hack_and_spec.rb",
50
+ "spec/fixnames/lowercase_spec.rb",
51
+ "spec/fixnames/semicolon_spec.rb",
52
+ "spec/fixnames/whitespace_spec.rb",
53
+ "spec/spec_helper.rb",
54
+ "spec/support/should_fix_helpers.rb",
55
+ "test/helper.rb",
56
+ "test/test_charstrip.rb",
57
+ "test/test_hack_and.rb",
58
+ "test/test_lowercase.rb",
59
+ "test/test_semicolon.rb",
60
+ "test/test_whitespace.rb"
61
+ ]
62
+ s.homepage = %q{http://github.com/pdkl95/fixnames}
63
+ s.licenses = [%q{MIT}]
64
+ s.require_paths = [%q{lib}]
65
+ s.rubygems_version = %q{1.8.5}
66
+ s.summary = %q{Filename cleanup for script compatability}
67
+
68
+ if s.respond_to? :specification_version then
69
+ s.specification_version = 3
70
+
71
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
72
+ s.add_runtime_dependency(%q<term-ansicolor>, [">= 1.0.6"])
73
+ s.add_development_dependency(%q<yard>, [">= 0.6.0"])
74
+ s.add_development_dependency(%q<rspec>, [">= 2.3.0"])
75
+ s.add_development_dependency(%q<jeweler>, [">= 1.6.4"])
76
+ s.add_development_dependency(%q<simplecov>, [">= 0"])
77
+ else
78
+ s.add_dependency(%q<term-ansicolor>, [">= 1.0.6"])
79
+ s.add_dependency(%q<yard>, [">= 0.6.0"])
80
+ s.add_dependency(%q<rspec>, [">= 2.3.0"])
81
+ s.add_dependency(%q<jeweler>, [">= 1.6.4"])
82
+ s.add_dependency(%q<simplecov>, [">= 0"])
83
+ end
84
+ else
85
+ s.add_dependency(%q<term-ansicolor>, [">= 1.0.6"])
86
+ s.add_dependency(%q<yard>, [">= 0.6.0"])
87
+ s.add_dependency(%q<rspec>, [">= 2.3.0"])
88
+ s.add_dependency(%q<jeweler>, [">= 1.6.4"])
89
+ s.add_dependency(%q<simplecov>, [">= 0"])
90
+ end
91
+ end
92
+
@@ -0,0 +1,12 @@
1
+ module Fixnames
2
+ end
3
+
4
+ require 'fixnames/option'
5
+ require 'fixnames/debug'
6
+ require 'fixnames/interface'
7
+
8
+ module Fixnames
9
+ include Debug
10
+ end
11
+
12
+
@@ -0,0 +1,43 @@
1
+ require 'term/ansicolor'
2
+
3
+ module Fixnames
4
+ module Debug
5
+ class Color
6
+ extend Term::ANSIColor
7
+
8
+ def self.prefix(chr, cname)
9
+ #raise "#{cname.inspect}, #{send(cname).inspect}"
10
+ [Color.send(cname), Color.bold, "#{chr}#{chr}>", Color.clear].join
11
+ end
12
+
13
+ def self.puts_msg(str, chr, cname)
14
+ puts "#{prefix(chr, cname)} #{str}"
15
+ end
16
+ end
17
+
18
+ def bold(str)
19
+ [ Color.bold,
20
+ Color.yellow,
21
+ Color.on_blue,
22
+ str,
23
+ Color.clear
24
+ ].join
25
+ end
26
+
27
+ def warn(msg)
28
+ Color.puts_msg(msg, '*', :red) if @option.verbose > 0
29
+ end
30
+
31
+ def note(msg)
32
+ Color.puts_msg(msg, '!', :yellow) if @option.verbose > 0
33
+ end
34
+
35
+ def info(msg)
36
+ Color.puts_msg(msg, '-', :green) if @option.verbose > 1
37
+ end
38
+
39
+ def debug(msg)
40
+ Color.puts_msg(msg, '>', :cyan) if @option.verbose > 2
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,86 @@
1
+ require 'fixnames/debug'
2
+ require 'fixnames/helpers'
3
+ require 'fixnames/filters'
4
+ require 'fixnames/engine/scan_dir'
5
+
6
+ module Fixnames
7
+ # the main filtering-engine that fixes
8
+ # a single filename
9
+ class Engine
10
+ include Debug
11
+ include Helpers
12
+ include Filters
13
+
14
+ attr_reader :orig, :fixed, :dir, :option
15
+ alias_method :to_s, :fixed
16
+
17
+ # Creates an engine to fix a single filename.
18
+ #
19
+ # @param name [String] The filename to be fixed
20
+ # @param options [{Symbol => Object}] An options hash
21
+ def initialize(name, opts=Option.new)
22
+ @option = opts
23
+
24
+ @dir = File.dirname(name)
25
+ @orig = File.basename(name)
26
+
27
+ if option.recursive && File.directory?(@orig)
28
+ @scandir = ScanDir.new(@orig, option)
29
+ end
30
+
31
+ @fixed = @orig.dup
32
+
33
+ option.filter_order.each do |optname|
34
+ if option.send(optname) and respond_to?(optname)
35
+ debug "FILTER[:#{optname}]"
36
+ old = fixed.dup
37
+ case method(optname).arity
38
+ when 1 then send optname, option.send(optname)
39
+ when 0 then send optname
40
+ else raise "Unsupported arity in ##{optname}"
41
+ end
42
+ if old != fixed
43
+ debug "\t old -- #{old.inspect}"
44
+ debug "\t new -- #{fixed.inspect}"
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ def orig_path
51
+ "#{dir}/#{orig}"
52
+ end
53
+
54
+ def fixed_path
55
+ "#{dir}/#{fixed}"
56
+ end
57
+
58
+ def scandir_changed?
59
+ @scandir ? @scandir.changed? : false
60
+ end
61
+
62
+ def changed?
63
+ fixed != orig
64
+ end
65
+
66
+ def collision?
67
+ File.exists? fixed_path
68
+ end
69
+
70
+ def fix!
71
+ @scandir.fix! if @scandir
72
+
73
+ if changed?
74
+ if collision?
75
+ warn "NAME COLLISION: #{fixed_path.inspect}"
76
+ else
77
+ note "mv #{orig_path.inspect} #{fixed_path.inspect}"
78
+ File.rename orig_path, fixed_path unless option.pretend
79
+ end
80
+ else
81
+ info "no change: #{orig_path.inspect}"
82
+ end
83
+ self
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,34 @@
1
+ module Fixnames
2
+ class Engine
3
+ class ScanDir
4
+ attr_reader :option, :name, :base, :prefix
5
+ def initialize(dirname, opts)
6
+ raise "Not a directory: #{dirname}" unless File.directory?(dirname)
7
+ @name = File.realpath(dirname)
8
+ raise "Not a directory: #{name}" unless File.directory?(name)
9
+
10
+ @option = opts
11
+ end
12
+
13
+ def glob_str
14
+ "#{name}/#{option.dir_glob}"
15
+ end
16
+
17
+ def files
18
+ @files ||= Dir.glob(glob_str)
19
+ end
20
+
21
+ def engines
22
+ @engies ||= files.map do |name|
23
+ Engine.new(name, option)
24
+ end
25
+ end
26
+
27
+ def fix!
28
+ engines.map do |en|
29
+ en.fix!
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,79 @@
1
+ module Fixnames
2
+ module Filters
3
+ def expunge(re)
4
+ replace re, option.mendstr
5
+ end
6
+
7
+ def hack_and
8
+ replace '&', '_and_'
9
+ end
10
+
11
+ def semicolon
12
+ translate ';', '-'
13
+ fixed.squeeze! '-'
14
+ end
15
+
16
+ def banners
17
+ option.banner_types.each do |x|
18
+ remove_bracket_ranges(x)
19
+ end
20
+ end
21
+
22
+ def brackets
23
+ remove wrap_brackets('.+?')
24
+ end
25
+
26
+ def checksums
27
+ remove wrap_brackets('[0-9a-fA-F]{8}')
28
+ end
29
+
30
+ def lowercase
31
+ translate 'A-Z', 'a-z'
32
+ end
33
+
34
+ def fix_dots
35
+ last = fixed.rindex('.')
36
+ translate '.', '_'
37
+ replace '(.*)\.(.*\.)', '\1_\2'
38
+ fixed[last] = '.' if last
39
+ end
40
+
41
+ def fix_dashes
42
+ fixed.squeeze! '-'
43
+ remove '^-' while fixed =~ /^-/
44
+ remove '-$' while fixed =~ /-$/
45
+ end
46
+
47
+ def camelcase
48
+ replace '([a-z])([A-Z])', '\1_\2'
49
+ fixed.downcase!
50
+ end
51
+
52
+ def junkwords(wordlist)
53
+ wordlist.each do |word|
54
+ replace "[_-]#{word}[_-]", '_'
55
+ remove "[_-]#{word}$"
56
+ remove "^#{word}[_-]"
57
+ end
58
+ end
59
+
60
+ def whitespace(chrlist)
61
+ replace "[#{Regexp.escape chrlist}]", '_'
62
+ replace '[_-]\.', '.' while fixed =~ /[_-]\./
63
+ replace '_-', '-' while fixed =~ /_-/
64
+ replace '-_', '-' while fixed =~ /-_/
65
+ remove '^_' while fixed =~ /^_/
66
+ remove '_$' while fixed =~ /_$/
67
+ fixed.squeeze! '_'
68
+ end
69
+
70
+ def charstrip(chrlist)
71
+ re = Regexp.escape( if option.charstrip_allow_brackets
72
+ remove_bracket_characters_from(chrlist)
73
+ else
74
+ chrlist
75
+ end )
76
+ remove "[#{re}]"
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,41 @@
1
+ module Fixnames
2
+ module Helpers
3
+ def replace(re, replacement)
4
+ re_str = bold "/#{re}/"
5
+ replacement_str = bold "\"#{replacement}\""
6
+ debug "\t<replace> #{re_str} -> #{replacement_str}"
7
+ fixed.gsub! Regexp.new(re), replacement
8
+ end
9
+
10
+ def remove(re)
11
+ re_str = bold "/#{re}/"
12
+ debug "\t<expunge> #{re_str}"
13
+ fixed.gsub! Regexp.new(re), ''
14
+ end
15
+
16
+ def translate(src, dst)
17
+ debug "\t<translate> #{bold src.inspect} -> #{bold dst.inspect}"
18
+ fixed.tr! src, dst
19
+ end
20
+
21
+ def match_bracket_open
22
+ "[#{Regexp.escape(option.bracket_characters_open)}]"
23
+ end
24
+
25
+ def match_bracket_close
26
+ "[#{Regexp.escape(option.bracket_characters_close)}]"
27
+ end
28
+
29
+ def wrap_brackets(re)
30
+ "#{match_bracket_open}#{re}#{match_bracket_close}"
31
+ end
32
+
33
+ def remove_bracket_ranges(re)
34
+ remove wrap_brackets(".*?#{re}.*?")
35
+ end
36
+
37
+ def remove_bracket_characters_from(str)
38
+ str.gsub /(#{match_bracket_open}|#{match_bracket_close})/, ''
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,41 @@
1
+ require 'fixnames/engine'
2
+
3
+ module Fixnames
4
+ module FixFile
5
+ def self.parse(name, opts)
6
+ Engine.new(name, opts)
7
+ end
8
+
9
+ # Returns the fixed version of a filename, without
10
+ # actually changing anything on the filesystem.
11
+ def self.fix_name(*args)
12
+ parse(*args).fixed
13
+ end
14
+
15
+ def self.fix!(*args)
16
+ parse(*args).fix!
17
+ end
18
+
19
+ def self.fix_list!(list, *args)
20
+ list.map do |x|
21
+ fix! x, *args
22
+ end
23
+ end
24
+ end
25
+
26
+ module FixDir
27
+ def self.parse(name, optsw)
28
+ Engine::ScanDir.new(name, opts)
29
+ end
30
+
31
+ def self.fix!(*args)
32
+ parse(*args).fix!
33
+ end
34
+
35
+ def self.fix_list!(list, *args)
36
+ list.map do |x|
37
+ fix! x, *args
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,163 @@
1
+ module Fixnames
2
+ class Option
3
+ # filters that MUST run early
4
+ SETUP_FILTERS = [ :expunge ]
5
+
6
+ # filters that only accept a simple boolean on/off
7
+ FLAG_FILTERS = [ :hack_and, :checksums, :banners,
8
+ :brackets, :semicolon,
9
+ :camelcase, :lowercase,
10
+ :fix_dots, :fix_dashes ]
11
+
12
+ # filters that accept character ranges
13
+ CHAR_FILTERS = [ :junkwords, :charstrip, :whitespace]
14
+
15
+ # standard order to apply the filters
16
+ DEFAULT_FILTER_ORDER = [ SETUP_FILTERS,
17
+ FLAG_FILTERS,
18
+ CHAR_FILTERS
19
+ ].flatten
20
+
21
+ DEFAULT_DIR_GLOB = '*'
22
+ DEFAULT_MENDSTR = ''
23
+ DEFAULT_WHITESPACE = " \t_"
24
+ DEFAULT_BRACKET_CHARACTERS_OPEN = '[({<'
25
+ DEFAULT_BRACKET_CHARACTERS_CLOSE = '])}>'
26
+ DEFAULT_CHARSTRIP = "[]{}'\",()+!~@#/\\"
27
+ DEFAULT_JUNKWORDS = [ 'x264', 'hdtv', '2hd', '720p', 'dvdrip']
28
+ DEFAULT_BANNER_TYPES = [ 'xxx', 'dvdrip', 'dual_audio',
29
+ 'xvid', 'h264', 'divx' ]
30
+
31
+ # Creates an option
32
+ #
33
+ # @param [String] name the name of the option to create
34
+ # @param [Array] types a list of classes that are valid
35
+ # @param [Object] default_val the value to set initially
36
+ def self.mkopt(name, types, default_val)
37
+ types = [types] unless types.is_a?(Array)
38
+ var = "@#{name}"
39
+
40
+ define_method "valid_for_#{name}?" do |value|
41
+ types.map do |type|
42
+ value.is_a?(type)
43
+ end.reduce(false) do |t,x|
44
+ t || x
45
+ end
46
+ end
47
+
48
+ define_method name do |*args|
49
+ unless instance_variable_defined?(var)
50
+ instance_variable_set(var, default_val)
51
+ end
52
+ if args.length == 1
53
+ unless send("valid_for_#{name}?", args[0])
54
+ raise "bad type for option"
55
+ end
56
+ instance_variable_set(var, args[0])
57
+ end
58
+ instance_variable_get(var)
59
+ end
60
+
61
+ define_method "#{name}=" do |value|
62
+ unless send("valid_for_#{name}?", value)
63
+ raise "bad type for option"
64
+ end
65
+ instance_variable_set(var, value)
66
+ end
67
+ end
68
+
69
+ # set to turn of the ANSI-color output
70
+ # @macro [attach] mkopt
71
+ # default: `$3`
72
+ #
73
+ # @overload $1()
74
+ # @overload $1(new_value)
75
+ # @overload $1=(new_value)
76
+ # @param [$2] new_value
77
+ # @return [$2]
78
+ mkopt :nocolor, [TrueClass, FalseClass], false
79
+
80
+ # Verbosity levels
81
+ #
82
+ # * `verbose=0` ; no output
83
+ # * `verbose=1` ; only names that change are output
84
+ # * `verbose=2` ; all names are output with their change-status
85
+ # * `verbose=3` ; all *filters* are output as they run for debugging. Very noisy.
86
+ mkopt :verbose, Integer, 0
87
+
88
+ # When {#recursive} is set, use this pattern to glob each
89
+ # directory for files.
90
+ mkopt :dir_glob, String, DEFAULT_DIR_GLOB
91
+
92
+ # Recursively descend into directories if true.
93
+ mkopt :recursive, [TrueClass, FalseClass], false
94
+
95
+ # A generic pattern to remove from all filenames.
96
+ # @note Enables {Fixnames::Filters#expunge}
97
+ mkopt :expunge, String, nil
98
+
99
+ # After we {#expunge} a pattern, it is replaced with this string.
100
+ mkopt :mendstr, String, DEFAULT_MENDSTR
101
+
102
+ # @note Enables {Fixnames::Filters#hack_and}
103
+ mkopt :hack_and, [TrueClass, FalseClass], false
104
+
105
+ # @note Enables {Fixnames::Filters#checksums}
106
+ mkopt :checksums, [TrueClass, FalseClass], false
107
+
108
+ # @note Enables {Fixnames::Filters#banners}
109
+ mkopt :banners, [TrueClass, FalseClass], false
110
+
111
+ # @note Enables {Fixnames::Filters#brackets}
112
+ mkopt :brackets, [TrueClass, FalseClass], false
113
+
114
+ # @note Enables {Fixnames::Filters#semicolon}
115
+ mkopt :semicolon, [TrueClass, FalseClass], false
116
+
117
+ # @note Enables {Fixnames::Filters#fix_dots}
118
+ mkopt :fix_dots, [TrueClass, FalseClass], false
119
+
120
+ # @note Enables {Fixnames::Filters#fix_dashes}
121
+ mkopt :fix_dashes, [TrueClass, FalseClass], false
122
+
123
+ # @note Enables {Fixnames::Filters#camelcase}
124
+ mkopt :camelcase, [TrueClass, FalseClass], false
125
+
126
+ # @note Enables {Fixnames::Filters#lowercase}
127
+ mkopt :lowercase, [TrueClass, FalseClass], false
128
+
129
+ # @note Enables {Fixnames::Filters#junkwords} if non-nil
130
+ mkopt :junkwords, Array, DEFAULT_JUNKWORDS
131
+
132
+ # @note Enables {Fixnames::Filters#charstrip} if non-nil
133
+ mkopt :charstrip, String, DEFAULT_CHARSTRIP
134
+
135
+ # @note Enables {Fixnames::Filters#whitespace} if non-nil
136
+ mkopt :whitespace, String, DEFAULT_WHITESPACE
137
+
138
+ # The list of strings to find for removal in {Fixnames::Filters#banners}
139
+ mkopt :banner_types, Array, DEFAULT_BANNER_TYPES
140
+
141
+ # Set to true to have {Fixnames::Filters#charstrip} ignore its
142
+ # default behavior and allow the brackets through. This is
143
+ # potentially ignored if you change {#charstrip}.
144
+ mkopt :charstrip_allow_brackets, [TrueClass, FalseClass], false
145
+
146
+ # What is considered an *open* bracket in things
147
+ # like {#checksums} or {#brackets}
148
+ mkopt :bracket_characters_open, String, DEFAULT_BRACKET_CHARACTERS_OPEN
149
+
150
+ # What is considered a *close* bracket in things
151
+ # like {#checksums} or {#brackets}
152
+ mkopt :bracket_characters_close, String, DEFAULT_BRACKET_CHARACTERS_CLOSE
153
+
154
+ # The order we should apply the filter to the filename.
155
+ # This order is significant, and can dramatically affect
156
+ # the output.
157
+ mkopt :filter_order, Array, DEFAULT_FILTER_ORDER
158
+
159
+ # if set, we just pretend to work, and skip the final
160
+ # move command, so the filesystem is never altered
161
+ mkopt :pretend, [TrueClass, FalseClass], false
162
+ end
163
+ end