fixnames 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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