findr 0.0.4 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 264462a154498bd31f1314af9efa5dad2361ec1b
4
+ data.tar.gz: 160cc1ac2b4cbad3fa14d973566f516f57be7dac
5
+ SHA512:
6
+ metadata.gz: 7066b9fe2a434f04eaa5e5f591b33b30d9ca1c758ab54ca1297df406ba3eeeb1239cb16f2cb5040a5f20d9cd52d2404e627d80bc31a6041a337334c3daf78b05
7
+ data.tar.gz: 3cee5b2a3acb9242ab1895e02f4a9b73ecb17ea64ec1c73bb0b3d3ec60b00a164af9765c440df4c261a91bff102297f94c7921a5216cebe1972176d4a30a45ab
data/README.md CHANGED
@@ -1,3 +1,73 @@
1
- *findr* is a ruby find and replace tool for batch editing text files.
1
+ *findr* is a ruby find and replace tool for mass editing of text files using (advanced) regular expressions.
2
2
 
3
3
  [![Build Status](https://travis-ci.org/mstrauss/findr.svg)](https://travis-ci.org/mstrauss/findr)
4
+
5
+ Usage
6
+ -----
7
+
8
+ Usage: findr [options] <search regex> [<replacement string>]
9
+ -g, --glob FILE SEARCH GLOB e.g. "*.{rb,erb}"; glob relative to the current directory
10
+ -G, --global-glob GLOB e.g. "/**/*.{rb,erb}"; glob may escape the current directory
11
+ -i, --ignore-case do a case-insensitive search
12
+ -n, --number-of-context LINES number of lines in context to print
13
+ -x, --execute actually execute the replacement
14
+ -c, --coding FILE CODING SYSTEM e.g. "iso-8859-1"
15
+ -s, --save saves your options to .findr-config for future use
16
+ -C, --codings-list list available encodings
17
+ -v, --verbose verbose output
18
+
19
+ *findr* recursively searches for the given [regular expression](http://rubular.com) in all files matching the given [glob](http://www.ruby-doc.org/core-2.5.1/Dir.html#method-c-glob), **starting from the current subdirectory**.
20
+
21
+
22
+ Basic Examples
23
+ --------------
24
+
25
+ * To **search the term** 123.456 in all `txt`-files:
26
+
27
+ findr -g '*.txt' 123\\.456
28
+
29
+ The `*.txt` must be escaped (from the shell) by using apostrophes, otherwise the shell might expand it to the names of `txt` files in the current directory.
30
+
31
+ The dot must be escaped (from the regexp parser), otherwise is would stand for *any* character. To escape the dot---on most shells---you could either write two backslashes to feed one backslash into the program, or put the command under single apostrophes, like
32
+
33
+ findr '123\.456'
34
+
35
+
36
+ * To **replace the term** 123.456 by 33.0:
37
+
38
+ findr '123\.456' 33.0
39
+
40
+ The previous command shows a preview of what would happen. Adding `-x` executes the change and irreversibly alters your files:
41
+
42
+ findr 123\.456 33.0 -x
43
+
44
+
45
+ * To do a **case-insensitive search** for `word` use
46
+
47
+ findr '(?i:word)'
48
+
49
+
50
+ Advanced Examples
51
+ -----------------
52
+
53
+ * A more complicated example: *findr* allows to **use matches** in the replacement string and also allows the use of advanced regular expression features like **lazy matching**. Consider the following input file `example.txt`:
54
+
55
+ a = 99
56
+ b=123;
57
+ var = 44
58
+ d = 55 ;
59
+ x=
60
+
61
+ Say, you want to normalize it in a way that there is exactly one space before and after the equal sign and each line should end with an semicolon. One way to do it:
62
+
63
+ findr -g example.txt '(.*?)\s*=\s*(.*?)[\s;]*$' '\1 = \2;' -x
64
+
65
+ `\s` stands for any whitespace character. `.*?` lazily matches any character, until the following expression (here `[\s;]*$`) matches. The `$` sign is necessary to make the second lazy matcher `.*?` not *too* lazy. The lazy matcher gives the *minimal* string possible to fulfill the regular expression--even when this means aborting prematurely. So, we need to take the line end matcher `$` into the regexp.
66
+
67
+ This changes `example.txt` to:
68
+
69
+ a = 99;
70
+ b = 123;
71
+ var = 44;
72
+ d = 55;
73
+ x = ;
data/Rakefile CHANGED
@@ -1,5 +1,8 @@
1
- task :default => [:test]
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/rspec'
3
+
4
+ task :default => [:test, :spec]
2
5
 
3
6
  task :test do
4
- exit system "bin/findr -g '*.rb' VERSION"
7
+ fail("Command failed.") unless system("bin/findr -g '*.rb' VERSION")
5
8
  end
@@ -6,33 +6,30 @@ require "findr/version"
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "findr"
8
8
  s.version = Findr::VERSION.dup
9
- s.date = "2014-11-20"
10
- s.summary = "A ruby find and replace tool for batch editing text files."
9
+ s.licenses = ['MIT']
10
+ s.date = "2018-10-15"
11
+ s.summary = "A ruby find and replace tool for mass editing text files."
11
12
  s.email = "Markus@ITstrauss.eu"
12
13
  s.homepage = "https://github.com/mstrauss/findr"
13
14
  s.authors = ['Markus Strauss']
14
15
 
15
16
  s.description = <<-EOF
16
- A ruby find and replace tool for batch editing text files.
17
+ A ruby find and replace tool for mass editing of text files.
17
18
  EOF
18
19
 
19
20
  dependencies = [
20
- [:test, 'rake']
21
- # Examples:
22
- # [:runtime, "rack", "~> 1.1"],
23
- # [:development, "rspec", "~> 2.1"],
21
+ [:development, 'rake', '~> 10.0'],
22
+ [:development, 'rspec', "~> 3.1"],
23
+ [:development, 'rake-rspec', "~> 0.0.2"],
24
+ [:development, 'bundler', "~>1.10"]
24
25
  ]
25
26
 
26
- s.files = Dir['**/*']
27
+ s.files = Dir['**/*']-Dir['*.lock','*.txt','*.gem']
27
28
  s.test_files = Dir['test/**/*'] + Dir['spec/**/*']
28
29
  s.executables = Dir['bin/*'].map { |f| File.basename(f) }
29
30
  s.require_paths = ["lib"]
30
31
 
31
-
32
- ## Make sure you can build the gem on older versions of RubyGems too:
33
- s.rubygems_version = "1.8.11"
34
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
35
- s.specification_version = 3 if s.respond_to? :specification_version
32
+ s.required_ruby_version = '>= 1.8.7'
36
33
 
37
34
  dependencies.each do |type, name, version|
38
35
  if s.respond_to?("add_#{type}_dependency")
@@ -1,2 +1,15 @@
1
1
  $:.unshift(File.dirname(__FILE__)) unless
2
2
  $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ module Findr
5
+ FIRST_RUBY_WITHOUT_ICONV = '1.9'
6
+ end
7
+
8
+ require 'iconv' if RUBY_VERSION < Findr::FIRST_RUBY_WITHOUT_ICONV
9
+
10
+ require 'findr/version'
11
+ require 'findr/error'
12
+ require 'findr/strategy_proxy'
13
+ require 'findr/encoder/iconv'
14
+ require 'findr/encoder/string'
15
+ require 'findr/encoder'
@@ -5,9 +5,6 @@ require 'optparse'
5
5
  require 'yaml'
6
6
  require 'pp'
7
7
 
8
- require 'findr/version'
9
- require 'findr/encoder'
10
-
11
8
  module Findr
12
9
 
13
10
  class CLI
@@ -22,10 +19,14 @@ module Findr
22
19
  def green(text); colorize(text, 32); end
23
20
  def yellow(text); colorize(text, 33); end
24
21
  def blue(text); colorize(text, 34); end
22
+ def gray(text); colorize(text, 2); end
23
+ def bold(text); colorize(text, '1;30'); end
24
+ def bold_yellow(text); colorize(text, '1;33'); end
25
+ def blue_bold(text); colorize(text, '1;34'); end
25
26
 
26
27
  def banner
27
28
  red( "FINDR VERSION #{Findr::VERSION}. THIS PROGRAM COMES WITH NO WARRANTY WHATSOEVER. MAKE BACKUPS!") + $/ +
28
- "Usage: #{Pathname($0).basename} [options] <search regex> [<replacement string>]"
29
+ bold( "Usage: #{Pathname($0).basename} [options] <search regex> [<replacement string>]" )
29
30
  end
30
31
 
31
32
  def show_usage
@@ -34,26 +35,32 @@ module Findr
34
35
  end
35
36
 
36
37
  def execute(stdout, arguments=[])
38
+
39
+ @stdout = stdout
40
+
37
41
  # unless arguments[0]
38
42
  # end
39
43
 
40
44
  # default options
41
45
  options = {}
42
- options[:glob] = '*'
43
46
  options[:coding] = 'utf-8'
44
-
45
- # options from file, if present
46
- if File.exists?( CONFIGFILE )
47
- file_options = YAML.load_file(CONFIGFILE)
48
- file_options.delete(:save)
49
- options.merge!( file_options )
50
- stdout.puts green "Using #{CONFIGFILE}."
51
- end
47
+ @context_lines = 0
52
48
 
53
49
  # parse command line
54
50
  @option_parser = OptionParser.new do |opts|
55
- opts.on('-g', '--glob FILE SEARCH GLOB', 'e.g. "*.{rb,erb}"') do |glob|
51
+ opts.on('-g', '--glob FILE SEARCH GLOB', 'e.g. "*.{rb,erb}"; glob relative to the current directory') do |glob|
56
52
  options[:glob] = glob
53
+ fail "-g option cannot be combined with -G" if options[:gglob]
54
+ end
55
+ opts.on('-G', '--global-glob GLOB', 'e.g. "/**/*.{rb,erb}"; glob may escape the current directory') do |glob|
56
+ options[:gglob] = glob
57
+ fail "-G option cannot be combined with -g" if options[:glob]
58
+ end
59
+ opts.on('-i', '--ignore-case', 'do a case-insensitive search') do
60
+ options[:re_opt_i] = true
61
+ end
62
+ opts.on('-n', '--number-of-context LINES', 'number of lines in context to print') do |lines|
63
+ @context_lines = lines.to_i
57
64
  end
58
65
  opts.on('-x', '--execute', 'actually execute the replacement') do
59
66
  options[:force] = true
@@ -68,10 +75,18 @@ module Findr
68
75
  pp Encoder.list
69
76
  exit
70
77
  end
78
+ opts.on('-v', '--verbose', "verbose output") do
79
+ @verbose = true
80
+ end
71
81
  end
72
82
  @option_parser.banner = self.banner
73
83
  @option_parser.parse!( arguments )
74
84
 
85
+ # default (local) glob
86
+ if !options[:glob] && !options[:gglob]
87
+ options[:glob] = '*'
88
+ end
89
+
75
90
  # optionally save the configuration to file
76
91
  if options[:save]
77
92
  options.delete(:save)
@@ -79,12 +94,23 @@ module Findr
79
94
  File.open( CONFIGFILE, 'w' ) { |file| YAML::dump( options, file ) }
80
95
  stdout.puts green "Saved options to file #{CONFIGFILE}."
81
96
  exit
97
+ else
98
+ # options from file, if present
99
+ if File.exists?( CONFIGFILE )
100
+ file_options = YAML.load_file(CONFIGFILE)
101
+ file_options.delete(:save)
102
+ options.merge!( file_options )
103
+ stdout.puts green "Using #{CONFIGFILE}."
104
+ end
82
105
  end
83
106
 
107
+ # build default global glob from local glob, if necessary
108
+ options[:gglob] = '**/' + options[:glob] unless options[:gglob]
109
+
84
110
  show_usage if arguments.size == 0
85
111
  arguments.clone.each do |arg|
86
112
  if !options[:find]
87
- options[:find] = Regexp.compile( arg )
113
+ options[:find] = Regexp.compile( arg, options[:re_opt_i] )
88
114
  arguments.delete_at(0)
89
115
  elsif !options[:replace]
90
116
  options[:replace] = arg
@@ -95,7 +121,7 @@ module Findr
95
121
  end
96
122
 
97
123
  show_usage if arguments.size != 0
98
- stdout.puts green "File inclusion glob: " + options[:glob]
124
+ stdout.puts green "File inclusion glob: " + options[:gglob]
99
125
  stdout.puts green "Searching for regex " + options[:find].to_s
100
126
 
101
127
  # some statistics
@@ -107,19 +133,27 @@ module Findr
107
133
 
108
134
  coder = Encoder.new( options[:coding] )
109
135
 
110
- Pathname.glob("**/#{options[:glob]}").each do |current_file|
136
+ verbose "Building file tree..."
137
+ files = Pathname.glob("#{options[:gglob]}")
138
+ verbose [' ', files.count, ' files found.']
139
+ files.each do |current_file|
111
140
  next unless current_file.file?
141
+ verbose ["Reading file ", current_file]
112
142
  stats[:total_files] += 1
113
143
  stats[:local_hits] = 0
114
144
  firstmatch = true
115
145
  linenumber = 0
116
- tempfile = Tempfile.new( 'current_file.basename' ) if options[:replace] && options[:force]
146
+ tempfile = Tempfile.new( 'current_file.basename' ) and verbose([' Create tempfile ',tempfile.path]) if options[:replace] && options[:force]
147
+ clear_context(); print_post_context = 0
117
148
  current_file.each_line do |l|
118
149
  begin
119
- l = coder.decode(l)
150
+ l, coding = coder.decode(l)
120
151
  rescue Encoder::Error
121
152
  stdout.puts "Skipping file #{current_file} because of error on line #{linenumber}: #{$!.original.class} #{$!.original.message}"
122
- tempfile.unlink if tempfile
153
+ if tempfile
154
+ tempfile_path = tempfile.path
155
+ tempfile.unlink and verbose([' Delete tempfile ', tempfile_path])
156
+ end
123
157
  break
124
158
  end
125
159
  linenumber += 1
@@ -128,24 +162,40 @@ module Findr
128
162
  if firstmatch
129
163
  stdout.puts red("#{current_file.cleanpath}:")
130
164
  end
131
- stdout.write( yellow( "%6d:" % [linenumber, l] ) )
132
- stdout.puts l
165
+ if @context_lines > 0
166
+ pop_context.map do |linenumber, l|
167
+ print_line( linenumber, l, coding, false )
168
+ end
169
+ print_post_context = @context_lines
170
+ end
171
+ print_line( linenumber, l.gsub( /(#{options[:find]})/, bold('\1') ), coding, :bold )
133
172
  firstmatch = false
134
173
  if options[:replace]
135
- stdout.write( blue( "%6d:" % [linenumber, l] ) )
136
174
  l_repl = l.gsub( options[:find], options[:replace] )
137
- tempfile.puts coder.encode(l_repl) if tempfile
138
- stdout.puts blue l_repl
175
+ tempfile.puts coder.encode(l_repl, coding) if tempfile
139
176
  replacement_done = true
177
+ print_line( linenumber, l_repl, coding, :blue )
178
+ end
179
+ else
180
+ if tempfile
181
+ tempfile.puts coder.encode(l, coding)
182
+ end
183
+ if print_post_context > 0
184
+ print_post_context -= 1
185
+ print_line( linenumber, l, coding )
186
+ else
187
+ push_context([linenumber, l])
140
188
  end
141
- elsif tempfile
142
- tempfile.puts coder.encode(l)
143
189
  end
144
190
  end
145
191
  if tempfile
146
192
  tempfile.close
147
- FileUtils.cp( tempfile.path, current_file ) if stats[:local_hits] > 0
148
- tempfile.unlink
193
+ if stats[:local_hits] > 0
194
+ FileUtils.cp( tempfile.path, current_file )
195
+ verbose([' Copy tempfile ', tempfile.path, ' to ', current_file])
196
+ end
197
+ tempfile_path = tempfile.path
198
+ tempfile.unlink and verbose([' Delete tempfile ', tempfile_path])
149
199
  end
150
200
 
151
201
  if stats[:local_hits] > 0
@@ -159,6 +209,44 @@ module Findr
159
209
  stdout.puts green( "#{stats[:total_hits]} occurences (lines) in #{stats[:hit_files]} of #{stats[:total_files]} files found." )
160
210
  end
161
211
 
212
+ def print_line( linenumber, line, coding, color = nil )
213
+ case color
214
+ when :bold
215
+ @stdout.write( bold_yellow( "%6d: " % [linenumber] ) )
216
+ @stdout.write gray("(coding: #{coding}) ")
217
+ @stdout.puts line
218
+ when :blue
219
+ @stdout.write( blue( "%6d: " % [linenumber] ) )
220
+ @stdout.write gray("(coding: #{coding}) ")
221
+ @stdout.puts blue line
222
+ else
223
+ @stdout.write( yellow( "%6d: " % [linenumber] ) )
224
+ @stdout.write gray("(coding: #{coding}) ")
225
+ @stdout.puts line
226
+ end
227
+ end
228
+
229
+ def clear_context
230
+ @context = []
231
+ end
232
+
233
+ def push_context(line)
234
+ @context.shift if @context.length >= @context_lines
235
+ @context.push(line)
236
+ end
237
+
238
+ def pop_context
239
+ return [] unless @context_lines > 0
240
+ c = @context
241
+ clear_context()
242
+ return c
243
+ end
244
+
245
+ def verbose(text_or_arry)
246
+ @stdout.puts( gray( Array(text_or_arry).join ) ) if @verbose
247
+ end
248
+
249
+
162
250
  end
163
251
 
164
252
  end
@@ -1,57 +1,21 @@
1
- FIRST_RUBY_WITHOUT_ICONV = '1.9'
2
- require 'iconv' if RUBY_VERSION < FIRST_RUBY_WITHOUT_ICONV
3
-
4
1
  module Findr
5
2
 
6
- # Class for wrapping original exceptions, which could be from Iconv (Ruby 1.8)
7
- # or String (Ruby >=1.9).
8
- # ()
9
- class Error < ::StandardError
10
- attr_reader :original
11
- def initialize(msg, original=$!)
12
- super(msg)
13
- @original = original;
14
- end
15
- end
16
-
17
3
  # Wrapper class for String#encode (Ruby >=1.9) and Iconv#iconv (Ruby 1.8).
18
4
  class Encoder
19
5
 
20
6
  class Error < Findr::Error; end
21
7
 
22
- class <<self
23
- def list
24
- return Iconv.list if RUBY_VERSION < FIRST_RUBY_WITHOUT_ICONV
25
- return Encoding.list.map(&:to_s)
26
- end
27
- end
8
+ include StrategyProxy
9
+ @@strategy = RUBY_VERSION < Findr::FIRST_RUBY_WITHOUT_ICONV ? Encoder::Iconv : Encoder::String
28
10
 
29
- def initialize( other_coding )
30
- if RUBY_VERSION < FIRST_RUBY_WITHOUT_ICONV
31
- @coding_to_utf8 = Iconv.new('UTF-8', other_coding)
32
- @utf8_to_coding = Iconv.new(other_coding, 'UTF-8')
33
- else
34
- @other_coding = Encoding.find(other_coding)
35
- end
36
- end
11
+ provides :decode, :string
12
+ provides :encode, :string, :into_coding
13
+ singleton_provides :list
37
14
 
38
- # Encodes given +string+ from +@other_coding+ to utf8.
39
- def decode( string )
40
- return @coding_to_utf8.iconv(string) if RUBY_VERSION < FIRST_RUBY_WITHOUT_ICONV
41
- string.force_encoding(@other_coding)
42
- fail Error.new("Encoding '#{@other_coding}' is invalid.") unless string.valid_encoding?
43
- return string.encode('UTF-8')
44
- rescue
45
- raise Error, "Error when decoding from '#{@other_coding}' into 'UTF-8'."
15
+ def initialize( other_codings )
16
+ @strategy = @@strategy.new(other_codings)
46
17
  end
47
18
 
48
- # Encodes given utf8 +string+ into +@other_coding+.
49
- def encode( string )
50
- return @utf8_to_coding.iconv(string) if RUBY_VERSION < FIRST_RUBY_WITHOUT_ICONV
51
- return string.encode(@other_coding)
52
- rescue
53
- raise Error, "Error when encoding from 'UTF-8' into '#{@other_coding}'."
54
- end
55
19
  end
56
20
 
57
21
  end
@@ -0,0 +1,43 @@
1
+ class Findr::Encoder
2
+
3
+ class Iconv
4
+ def initialize( other_coding )
5
+ @other_coding = other_coding.split(',')
6
+ end
7
+
8
+ # Encodes given +string+ from +@other_coding+ to utf8.
9
+ def decode( string )
10
+ coding = nil
11
+ coded_string = nil
12
+ have_valid_coding = @other_coding.any? do |c|
13
+ begin
14
+ coded_string = ::Iconv.conv('UTF-8', c, string)
15
+ coding = c
16
+ true
17
+ rescue
18
+ false
19
+ end
20
+ end
21
+ fail Error.new("No valid coding given.") unless have_valid_coding
22
+ return [coded_string, coding.to_s.upcase]
23
+ rescue
24
+ raise Error, "Error when decoding from '#{@other_coding}' into 'UTF-8': #{$!}"
25
+ return
26
+ end
27
+
28
+ # Encodes given utf8 +string+ into +coding+.
29
+ def encode( string, coding )
30
+ return ::Iconv.conv(coding, 'UTF-8', string)
31
+ rescue
32
+ raise Error, "Error when encoding from 'UTF-8' into '#{coding}'."
33
+ end
34
+
35
+ # Returns a list of valid encodings
36
+ def self.list
37
+ return ::Iconv.list
38
+ rescue
39
+ fail Error, "Iconv.list not supported on Ruby #{RUBY_VERSION}. Try 'iconv -l' on the command line."
40
+ end
41
+ end
42
+
43
+ end
@@ -0,0 +1,35 @@
1
+ class Findr::Encoder
2
+
3
+ class String
4
+ def initialize( other_coding )
5
+ @other_coding = other_coding.split(',').map {|coding| Encoding.find(coding)}
6
+ end
7
+
8
+ # Encodes given +string+ from +@other_coding+ to utf8.
9
+ def decode( string )
10
+ coding = nil
11
+ have_valid_coding = @other_coding.any? do |c|
12
+ string.force_encoding(c)
13
+ coding = c
14
+ string.valid_encoding?
15
+ end
16
+ fail Error.new("No valid coding given.") unless have_valid_coding
17
+ return [string.encode('UTF-8'), coding.to_s]
18
+ rescue
19
+ raise Error, "Error when decoding from '#{@other_coding}' into 'UTF-8': #{$!}"
20
+ end
21
+
22
+ # Encodes given utf8 +string+ into +coding+.
23
+ def encode( string, coding )
24
+ return string.encode(coding)
25
+ rescue
26
+ raise Error, "Error when encoding from 'UTF-8' into '#{coding}'."
27
+ end
28
+
29
+ # Returns a list of valid encodings
30
+ def self.list
31
+ return Encoding.list.map(&:to_s)
32
+ end
33
+ end
34
+
35
+ end
@@ -0,0 +1,14 @@
1
+ module Findr
2
+
3
+ # Class for wrapping original exceptions, which could be from Iconv (Ruby 1.8)
4
+ # or String (Ruby >=1.9).
5
+ # ()
6
+ class Error < ::StandardError
7
+ attr_reader :original
8
+ def initialize(msg, original=$!)
9
+ super(msg)
10
+ @original = original;
11
+ end
12
+ end
13
+
14
+ end
@@ -0,0 +1,53 @@
1
+ module Findr
2
+ # +StrategyProxy+
3
+ # based on [AbstractInterface by Mark Bates](http://metabates.com/2011/02/07/building-interfaces-and-abstract-classes-in-ruby/)
4
+ # and on [Contractual by Joseph Weissman](https://rubygems.org/gems/contractual)
5
+ #
6
+ # +include StrategyProxy+ to declare a class to be a +StrategyProxy+.
7
+ # Also you need to define +@strategy+ in the initializer of your class and +@@strategy++ if you want to use +singleton_provides+.
8
+ module StrategyProxy
9
+
10
+ class MethodNotImplementedError < NoMethodError; end
11
+
12
+ def self.included(klass)
13
+ klass.send(:include, StrategyProxy::Methods)
14
+ klass.send(:extend, StrategyProxy::Methods)
15
+ klass.send(:extend, StrategyProxy::ClassMethods)
16
+ end
17
+
18
+ module Methods
19
+ def does_not_implement_method(klass, method_name = nil)
20
+ klass_name = klass.kind_of?(Class) ? klass.name : klass.class.name
21
+ raise MethodNotImplementedError.new("#{klass_name} needs to implement '#{method_name}' for StrategyProxy #{self.class.name}!")
22
+ end
23
+ end
24
+
25
+ module ClassMethods
26
+ def provides(method_name, *argument_names)
27
+ arglist = argument_names.map(&:to_sym).join(',')
28
+ method_string = <<-END_METHOD
29
+ def #{method_name}(#{arglist})
30
+ @strategy.#{method_name}(#{arglist})
31
+ rescue NoMethodError
32
+ does_not_implement_method(@strategy, '#{method_name}')
33
+ end
34
+ END_METHOD
35
+ class_eval method_string
36
+ end
37
+
38
+ # before using +singleton_provides+ you must define +@@strategy+ BEFORE FIRST USE of the defined singleton methods
39
+ def singleton_provides(method_name, *argument_names)
40
+ arglist = argument_names.map(&:to_sym).join(',')
41
+ method_string = <<-END_METHOD
42
+ def self.#{method_name}(#{arglist})
43
+ @@strategy.#{method_name}(#{arglist})
44
+ rescue NoMethodError
45
+ does_not_implement_method(@@strategy, 'self.#{method_name}')
46
+ end
47
+ END_METHOD
48
+ class_eval method_string
49
+ end
50
+ end
51
+
52
+ end
53
+ end
@@ -1,3 +1,3 @@
1
1
  module Findr
2
- VERSION = '0.0.4'
2
+ VERSION = '0.0.7'
3
3
  end
Binary file
@@ -0,0 +1,71 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ def skip_new_ruby
5
+ skip "Incorrect Ruby version." if RUBY_VERSION > '1.9'
6
+ end
7
+
8
+ def skip_old_ruby
9
+ skip "Incorrect Ruby version." if RUBY_VERSION < '1.9'
10
+ end
11
+
12
+
13
+ module Findr
14
+
15
+ RSpec.describe Encoder do
16
+
17
+ let(:utf8_string) {'123ö'}
18
+ let(:latin1_string) {"123\xF6"}
19
+ let(:cp850_string) {"123\x94"}
20
+
21
+ describe '.list' do
22
+ subject {Encoder.list}
23
+
24
+ it 'fails on ruby 1.8' do
25
+ skip_new_ruby
26
+ expect{subject}.to raise_error(Encoder::Error) do |error|
27
+ expect(error.message).to include('Iconv.list not supported')
28
+ expect(error.original).to be_kind_of(NoMethodError)
29
+ expect(error.original.message).to eq("undefined method `list' for Iconv:Class")
30
+ end
31
+
32
+ end
33
+
34
+ it 'returns a list of supported encodings' do
35
+ skip_old_ruby
36
+ expect(subject).to include('UTF-8')
37
+ expect(subject).to include('ISO-8859-1')
38
+ end
39
+ end
40
+
41
+ describe 'decode' do
42
+
43
+ it 'should accept single codings' do
44
+ expect(Encoder.new('utf-8').decode(utf8_string)).to eq([utf8_string, 'UTF-8'])
45
+ expect(Encoder.new('iso-8859-1').decode(latin1_string)).to eq([utf8_string, 'ISO-8859-1'])
46
+ expect(Encoder.new('us-ascii').decode('123')).to eq(['123', 'US-ASCII'])
47
+ end
48
+
49
+ it 'should accept multiple encodings' do
50
+ expect(Encoder.new('ascii,cp850').decode(cp850_string)).to eq([utf8_string, 'CP850'])
51
+ end
52
+
53
+ it 'should fail if no valid coding can be found' do
54
+ expect{Encoder.new('ascii').decode("123\x94")}.to \
55
+ raise_error(Encoder::Error) do |error|
56
+ expect(error.original).to be_kind_of(Encoder::Error)
57
+ expect(error.original.message).to include('No valid coding given')
58
+ end
59
+ end
60
+ end
61
+
62
+ describe 'encode' do
63
+ it 'should accept single codings' do
64
+ expect(Encoder.new('utf-8').encode(utf8_string, 'utf-8')).to eq(utf8_string)
65
+ end
66
+
67
+ end
68
+
69
+ end
70
+
71
+ end
@@ -0,0 +1,92 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # The generated `.rspec` file contains `--require spec_helper` which will cause this
4
+ # file to always be loaded, without a need to explicitly require it in any files.
5
+ #
6
+ # Given that it is always loaded, you are encouraged to keep this file as
7
+ # light-weight as possible. Requiring heavyweight dependencies from this file
8
+ # will add to the boot time of your test suite on EVERY test run, even for an
9
+ # individual file that may not need all of that loaded. Instead, consider making
10
+ # a separate helper file that requires the additional dependencies and performs
11
+ # the additional setup, and require it from the spec files that actually need it.
12
+ #
13
+ # The `.rspec` file also contains a few flags that are not defaults but that
14
+ # users commonly want.
15
+ #
16
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
17
+ RSpec.configure do |config|
18
+ # rspec-expectations config goes here. You can use an alternate
19
+ # assertion/expectation library such as wrong or the stdlib/minitest
20
+ # assertions if you prefer.
21
+ config.expect_with :rspec do |expectations|
22
+ # This option will default to `true` in RSpec 4. It makes the `description`
23
+ # and `failure_message` of custom matchers include text for helper methods
24
+ # defined using `chain`, e.g.:
25
+ # be_bigger_than(2).and_smaller_than(4).description
26
+ # # => "be bigger than 2 and smaller than 4"
27
+ # ...rather than:
28
+ # # => "be bigger than 2"
29
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
30
+ end
31
+
32
+ # rspec-mocks config goes here. You can use an alternate test double
33
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
34
+ config.mock_with :rspec do |mocks|
35
+ # Prevents you from mocking or stubbing a method that does not exist on
36
+ # a real object. This is generally recommended, and will default to
37
+ # `true` in RSpec 4.
38
+ mocks.verify_partial_doubles = true
39
+ end
40
+
41
+ # The settings below are suggested to provide a good initial experience
42
+ # with RSpec, but feel free to customize to your heart's content.
43
+ # =begin
44
+ # These two settings work together to allow you to limit a spec run
45
+ # to individual examples or groups you care about by tagging them with
46
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
47
+ # get run.
48
+ config.filter_run :focus
49
+ config.run_all_when_everything_filtered = true
50
+
51
+ # Limits the available syntax to the non-monkey patched syntax that is recommended.
52
+ # For more details, see:
53
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
54
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
55
+ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
56
+ config.disable_monkey_patching!
57
+
58
+ # This setting enables warnings. It's recommended, but in some cases may
59
+ # be too noisy due to issues in dependencies.
60
+ config.warnings = true
61
+
62
+ # Many RSpec users commonly either run the entire suite or an individual
63
+ # file, and it's useful to allow more verbose output when running an
64
+ # individual spec file.
65
+ if config.files_to_run.one?
66
+ # Use the documentation formatter for detailed output,
67
+ # unless a formatter has already been configured
68
+ # (e.g. via a command-line flag).
69
+ config.default_formatter = 'doc'
70
+ end
71
+
72
+ # Print the 10 slowest examples and example groups at the
73
+ # end of the spec run, to help surface which specs are running
74
+ # particularly slow.
75
+ config.profile_examples = 10
76
+
77
+ # Run specs in random order to surface order dependencies. If you find an
78
+ # order dependency and want to debug it, you can fix the order by providing
79
+ # the seed, which is printed after each run.
80
+ # --seed 1234
81
+ config.order = :random
82
+
83
+ # Seed global randomization in this process using the `--seed` CLI option.
84
+ # Setting this allows you to use `--seed` to deterministically reproduce
85
+ # test failures related to randomization by passing the same `--seed` value
86
+ # as the one that triggered the failure.
87
+ Kernel.srand config.seed
88
+ # =end
89
+ end
90
+
91
+ require './lib/findr'
92
+ require "./lib/findr/cli"
metadata CHANGED
@@ -1,33 +1,72 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: findr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
5
- prerelease:
4
+ version: 0.0.7
6
5
  platform: ruby
7
6
  authors:
8
7
  - Markus Strauss
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2014-11-20 00:00:00.000000000 Z
11
+ date: 2018-10-15 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: rake
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ! '>='
17
+ - - "~>"
20
18
  - !ruby/object:Gem::Version
21
- version: '0'
22
- type: :runtime
19
+ version: '10.0'
20
+ type: :development
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
- - - ! '>='
24
+ - - "~>"
28
25
  - !ruby/object:Gem::Version
29
- version: '0'
30
- description: ! 'A ruby find and replace tool for batch editing text files.
26
+ version: '10.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.1'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake-rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.0.2
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.0.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.10'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.10'
69
+ description: 'A ruby find and replace tool for mass editing of text files.
31
70
 
32
71
  '
33
72
  email: Markus@ITstrauss.eu
@@ -36,38 +75,46 @@ executables:
36
75
  extensions: []
37
76
  extra_rdoc_files: []
38
77
  files:
78
+ - Gemfile
79
+ - README.md
80
+ - Rakefile
39
81
  - bin/findr
40
82
  - findr.gemspec
41
- - Gemfile
42
- - Gemfile.lock
83
+ - lib/findr.rb
43
84
  - lib/findr/cli.rb
44
85
  - lib/findr/encoder.rb
86
+ - lib/findr/encoder/iconv.rb
87
+ - lib/findr/encoder/string.rb
88
+ - lib/findr/error.rb
89
+ - lib/findr/strategy_proxy.rb
45
90
  - lib/findr/version.rb
46
- - lib/findr.rb
47
- - Rakefile
48
- - README.md
91
+ - pkg/findr-0.0.7.gem
92
+ - spec/findr/encoder_spec.rb
93
+ - spec/spec_helper.rb
49
94
  homepage: https://github.com/mstrauss/findr
50
- licenses: []
95
+ licenses:
96
+ - MIT
97
+ metadata: {}
51
98
  post_install_message:
52
99
  rdoc_options: []
53
100
  require_paths:
54
101
  - lib
55
102
  required_ruby_version: !ruby/object:Gem::Requirement
56
- none: false
57
103
  requirements:
58
- - - ! '>='
104
+ - - ">="
59
105
  - !ruby/object:Gem::Version
60
- version: '0'
106
+ version: 1.8.7
61
107
  required_rubygems_version: !ruby/object:Gem::Requirement
62
- none: false
63
108
  requirements:
64
- - - ! '>='
109
+ - - ">="
65
110
  - !ruby/object:Gem::Version
66
111
  version: '0'
67
112
  requirements: []
68
113
  rubyforge_project:
69
- rubygems_version: 1.8.23.2
114
+ rubygems_version: 2.5.2.1
70
115
  signing_key:
71
- specification_version: 3
72
- summary: A ruby find and replace tool for batch editing text files.
73
- test_files: []
116
+ specification_version: 4
117
+ summary: A ruby find and replace tool for mass editing text files.
118
+ test_files:
119
+ - spec/findr/encoder_spec.rb
120
+ - spec/spec_helper.rb
@@ -1,16 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- findr (0.0.4)
5
- rake
6
-
7
- GEM
8
- remote: https://rubygems.org/
9
- specs:
10
- rake (0.9.2.2)
11
-
12
- PLATFORMS
13
- ruby
14
-
15
- DEPENDENCIES
16
- findr!