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.
- checksums.yaml +7 -0
- data/README.md +71 -1
- data/Rakefile +5 -2
- data/findr.gemspec +10 -13
- data/lib/findr.rb +13 -0
- data/lib/findr/cli.rb +117 -29
- data/lib/findr/encoder.rb +7 -43
- data/lib/findr/encoder/iconv.rb +43 -0
- data/lib/findr/encoder/string.rb +35 -0
- data/lib/findr/error.rb +14 -0
- data/lib/findr/strategy_proxy.rb +53 -0
- data/lib/findr/version.rb +1 -1
- data/pkg/findr-0.0.7.gem +0 -0
- data/spec/findr/encoder_spec.rb +71 -0
- data/spec/spec_helper.rb +92 -0
- metadata +73 -26
- data/Gemfile.lock +0 -16
checksums.yaml
ADDED
@@ -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
|
1
|
+
*findr* is a ruby find and replace tool for mass editing of text files using (advanced) regular expressions.
|
2
2
|
|
3
3
|
[](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
data/findr.gemspec
CHANGED
@@ -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.
|
10
|
-
s.
|
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
|
17
|
+
A ruby find and replace tool for mass editing of text files.
|
17
18
|
EOF
|
18
19
|
|
19
20
|
dependencies = [
|
20
|
-
[:
|
21
|
-
|
22
|
-
|
23
|
-
|
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")
|
data/lib/findr.rb
CHANGED
@@ -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'
|
data/lib/findr/cli.rb
CHANGED
@@ -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[:
|
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
|
-
|
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
|
-
|
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
|
-
|
132
|
-
|
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
|
-
|
148
|
-
|
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
|
data/lib/findr/encoder.rb
CHANGED
@@ -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
|
-
|
23
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
39
|
-
|
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
|
data/lib/findr/error.rb
ADDED
@@ -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
|
data/lib/findr/version.rb
CHANGED
data/pkg/findr-0.0.7.gem
ADDED
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
|
data/spec/spec_helper.rb
ADDED
@@ -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.
|
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:
|
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: :
|
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
|
-
|
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
|
-
-
|
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
|
-
-
|
47
|
-
-
|
48
|
-
-
|
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:
|
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:
|
114
|
+
rubygems_version: 2.5.2.1
|
70
115
|
signing_key:
|
71
|
-
specification_version:
|
72
|
-
summary: A ruby find and replace tool for
|
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
|