findr 0.0.4 → 0.0.7

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,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!