spellr 0.9.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +40 -23
- data/lib/spellr/autocorrect_reporter.rb +39 -0
- data/lib/spellr/backports.rb +1 -44
- data/lib/spellr/base_reporter.rb +8 -0
- data/lib/spellr/check.rb +38 -11
- data/lib/spellr/cli_options.rb +8 -13
- data/lib/spellr/config.rb +11 -3
- data/lib/spellr/config_validator.rb +6 -6
- data/lib/spellr/file.rb +4 -0
- data/lib/spellr/file_list.rb +1 -1
- data/lib/spellr/interactive.rb +56 -19
- data/lib/spellr/interactive_add.rb +2 -2
- data/lib/spellr/interactive_replacement.rb +2 -2
- data/lib/spellr/key_tuner/possible_key.rb +1 -1
- data/lib/spellr/key_tuner/stats.rb +1 -1
- data/lib/spellr/line_tokenizer.rb +1 -1
- data/lib/spellr/reporter.rb +2 -2
- data/lib/spellr/suggester.rb +60 -0
- data/lib/spellr/token.rb +9 -0
- data/lib/spellr/tokenizer.rb +1 -1
- data/lib/spellr/version.rb +1 -1
- data/lib/spellr/wordlist.rb +13 -0
- data/spellr.gemspec +3 -1
- metadata +34 -6
- data/lib/spellr/check_interactive.rb +0 -24
- data/lib/spellr/check_parallel.rb +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 80439d65c02b1e49b28e0def2c52f45b103b7c1282a95940473dc37e90d4f3a6
|
4
|
+
data.tar.gz: 9028a62ab2ff949810ff64801ffa42752360bd2e0b45ad507975d9c9d839ef68
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5592e135615d089882db57f881a1d3745d06880611cfe181d36a21f4b718b5e91e799d7afe9f045e81629eb222db07046d072b0a88424d7bcc7526ba99f5fd44
|
7
|
+
data.tar.gz: aca7014f20d2638ee676499621765ab00f7e862d7be81d446685049ea350c42f25609bac9e998e5f2f4f2399372191c30c46ddd0f192293cafe45f0a6f7c65ff
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
# v0.10.0
|
2
|
+
- Drop ruby 2.4 support, to allow for...
|
3
|
+
- Spelling suggestions while using `spellr --interactive`
|
4
|
+
- And a new, probably frequently wrong, `spellr --autocorrect`
|
5
|
+
|
1
6
|
# v0.9.1
|
2
7
|
- Assume all files are utf8, more comprehensively. (Sets ::Encoding.default_external and default_internal while running)
|
3
8
|
|
data/README.md
CHANGED
@@ -27,7 +27,7 @@ However, in a programming context spelling things _consistently_ is useful, wher
|
|
27
27
|
|
28
28
|
## Installation
|
29
29
|
|
30
|
-
This is tested against ruby 2.
|
30
|
+
This is tested against ruby 2.5-3.0.
|
31
31
|
|
32
32
|
### With Bundler
|
33
33
|
|
@@ -66,6 +66,7 @@ $ spellr # will run the spell checker
|
|
66
66
|
$ spellr --interactive # will run the spell checker, interactively
|
67
67
|
$ spellr --wordlist # will output all words that fail the spell checker in spellr wordlist format
|
68
68
|
$ spellr --quiet # will suppress all output
|
69
|
+
$ spellr --autocorrect # for if you're feeling lucky
|
69
70
|
```
|
70
71
|
|
71
72
|
To check a single file or subset of files, just add paths or globs:
|
@@ -123,14 +124,16 @@ To start an interactive spell checking session:
|
|
123
124
|
$ spellr --interactive
|
124
125
|
```
|
125
126
|
|
126
|
-
You'll be shown each word that's not found in a dictionary, it's location (path:line:column), along with a prompt.
|
127
|
+
You'll be shown each word that's not found in a dictionary, it's location (path:line:column), along with suggestions, and a prompt.
|
127
128
|
```
|
128
129
|
file.rb:1:0 notaword
|
130
|
+
Did you mean: [1] notwork, [2] nonword
|
129
131
|
[a]dd, [r]eplace, [s]kip, [h]elp, [^C] to exit: [ ]
|
130
132
|
```
|
131
133
|
|
132
134
|
Type `h` for this list of what each letter command does
|
133
135
|
```
|
136
|
+
[1]...[2] Replace notaword with the numbered suggestion
|
134
137
|
[a] Add notaword to a word list
|
135
138
|
[r] Replace notaword
|
136
139
|
[R] Replace this and all future instances of notaword
|
@@ -144,9 +147,20 @@ What do you want to do? [ ]
|
|
144
147
|
|
145
148
|
---
|
146
149
|
|
150
|
+
If you type a numeral the word will be replaced with that numbered suggestion
|
151
|
+
```
|
152
|
+
file.txt:1:0 notaword
|
153
|
+
Did you mean: [1] notwork, [2] nonword
|
154
|
+
[a]dd, [r]eplace, [s]kip, [h]elp, [^C] to exit: [2]
|
155
|
+
Replaced notaword with nonword
|
156
|
+
```
|
157
|
+
|
158
|
+
---
|
159
|
+
|
147
160
|
If you type `r` or `R` you'll be shown a prompt with the original word and it prefilled ready for correcting:
|
148
161
|
```
|
149
162
|
file.txt:1:0 notaword
|
163
|
+
Did you mean: [1] notwork, [2] nonword
|
150
164
|
[a]dd, [r]eplace, [s]kip, [h]elp, [^C] to exit: [r]
|
151
165
|
|
152
166
|
[^C] to go back
|
@@ -167,6 +181,7 @@ Lowercase `s` will skip this particular use of the word, uppercase `S` will also
|
|
167
181
|
If you instead type `a` you'll be shown a list of possible wordlists to add to. This list is based on the file path, and is configurable in `.spellr.yml`.
|
168
182
|
```
|
169
183
|
file.txt:1:0 notaword
|
184
|
+
Did you mean: [1] notwork, [2] nonword
|
170
185
|
[a]dd, [r]eplace, [s]kip, [h]elp, [^C] to exit: [a]
|
171
186
|
|
172
187
|
[e] english
|
@@ -247,7 +262,7 @@ languages:
|
|
247
262
|
- path/to/logstash/file
|
248
263
|
```
|
249
264
|
|
250
|
-
## Rake
|
265
|
+
## Rake
|
251
266
|
|
252
267
|
Create or open a file in the root of your project named `Rakefile`.
|
253
268
|
adding the following lines
|
@@ -260,7 +275,27 @@ Spellr::RakeTask.generate_task
|
|
260
275
|
This will add the `rake spellr` task. To provide arguments like the cli, use square brackets. (ensure you escape the `[]` if you're using zsh)
|
261
276
|
`rake 'spellr[--interactive]'`
|
262
277
|
|
263
|
-
To
|
278
|
+
To provide default cli arguments, the first argument is the name, and subsequent arguments are the cli arguments.
|
279
|
+
```ruby
|
280
|
+
# Rakefile
|
281
|
+
require 'spellr/rake_task'
|
282
|
+
Spellr::RakeTask.generate_task(:spellr_quiet, '--quiet')
|
283
|
+
|
284
|
+
task default: :spellr_quiet
|
285
|
+
```
|
286
|
+
or `rake spellr` will be in interactive mode unless the CI env variable is set.
|
287
|
+
```ruby
|
288
|
+
# Rakefile
|
289
|
+
require 'spellr/rake_task'
|
290
|
+
spellr_arguments = ENV['CI'] ? [] : ['--interactive']
|
291
|
+
Spellr::RakeTask.generate_task(:spellr, **spellr_arguments)
|
292
|
+
|
293
|
+
task default: :spellr
|
294
|
+
```
|
295
|
+
|
296
|
+
## Travis
|
297
|
+
|
298
|
+
To have this automatically run on travis, add `:spellr` to the default rake task.
|
264
299
|
```ruby
|
265
300
|
# Rakefile
|
266
301
|
require 'spellr/rake_task'
|
@@ -284,28 +319,10 @@ sudo: false
|
|
284
319
|
language: ruby
|
285
320
|
cache: bundler
|
286
321
|
rvm:
|
287
|
-
-
|
322
|
+
- 3.0
|
288
323
|
before_install: gem install bundler
|
289
324
|
```
|
290
325
|
|
291
|
-
To provide default cli arguments, the first argument is the name, and subsequent arguments are the cli arguments.
|
292
|
-
```ruby
|
293
|
-
# Rakefile
|
294
|
-
require 'spellr/rake_task'
|
295
|
-
Spellr::RakeTask.generate_task(:spellr_quiet, '--quiet')
|
296
|
-
|
297
|
-
task default: :spellr_quiet
|
298
|
-
```
|
299
|
-
or `rake spellr` will be in interactive mode unless the CI env variable is set.
|
300
|
-
```ruby
|
301
|
-
# Rakefile
|
302
|
-
require 'spellr/rake_task'
|
303
|
-
spellr_arguments = ENV['CI'] ? [] : ['--interactive']
|
304
|
-
Spellr::RakeTask.generate_task(:spellr, **spellr_arguments)
|
305
|
-
|
306
|
-
task default: :spellr
|
307
|
-
```
|
308
|
-
|
309
326
|
## Ignoring the configured patterns
|
310
327
|
|
311
328
|
Sometimes you'll want to spell check something that would usually be ignored,
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../spellr'
|
4
|
+
require_relative 'base_reporter'
|
5
|
+
require_relative 'suggester'
|
6
|
+
|
7
|
+
module Spellr
|
8
|
+
class AutocorrectReporter < BaseReporter
|
9
|
+
def finish
|
10
|
+
puts "\n"
|
11
|
+
print_count(:checked, 'file')
|
12
|
+
print_value(total, 'error', 'found')
|
13
|
+
print_count(:total_fixed, 'error', 'fixed', hide_zero: true)
|
14
|
+
print_count(:total_unfixed, 'error', 'unfixed', hide_zero: true)
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(token)
|
18
|
+
super
|
19
|
+
|
20
|
+
handle_replace(token)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def total
|
26
|
+
counts[:total_unfixed] + counts[:total_fixed]
|
27
|
+
end
|
28
|
+
|
29
|
+
def handle_replace(token)
|
30
|
+
replacement = ::Spellr::Suggester.suggestions(token).first
|
31
|
+
return increment(:total_unfixed) unless replacement
|
32
|
+
|
33
|
+
token.replace(replacement)
|
34
|
+
increment(:total_fixed)
|
35
|
+
puts "Replaced #{red(token)} with #{green(replacement)}"
|
36
|
+
throw :check_file_from, token
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/spellr/backports.rb
CHANGED
@@ -1,50 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Spellr
|
4
|
-
|
5
|
-
unless ruby_version >= Gem::Version.new('2.5')
|
6
|
-
module HashSlice
|
7
|
-
refine Hash do
|
8
|
-
def slice!(*keys)
|
9
|
-
delete_if { |k| !keys.include?(k) }
|
10
|
-
end
|
11
|
-
|
12
|
-
def slice(*keys)
|
13
|
-
dup.slice!(*keys)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
require 'yaml'
|
19
|
-
module YAMLSymbolizeNames
|
20
|
-
refine YAML.singleton_class do
|
21
|
-
alias_method :safe_load_without_symbolize_names, :safe_load
|
22
|
-
def safe_load(path, *args, symbolize_names: false, **kwargs)
|
23
|
-
if symbolize_names
|
24
|
-
symbolize_names!(safe_load_without_symbolize_names(path, *args, **kwargs))
|
25
|
-
else
|
26
|
-
safe_load_without_symbolize_names(path, *args, **kwargs)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
private
|
31
|
-
|
32
|
-
def symbolize_names!(obj) # rubocop:disable Metrics/MethodLength
|
33
|
-
case obj
|
34
|
-
when Hash
|
35
|
-
obj.keys.each do |key| # rubocop:disable Style/HashEachMethods # each_key never finishes.
|
36
|
-
obj[key.to_sym] = symbolize_names!(obj.delete(key))
|
37
|
-
end
|
38
|
-
when Array
|
39
|
-
obj.map! { |ea| symbolize_names!(ea) }
|
40
|
-
end
|
41
|
-
obj
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
unless ruby_version >= Gem::Version.new('2.6')
|
4
|
+
unless Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.6')
|
48
5
|
require 'yaml'
|
49
6
|
module YAMLPermittedClasses
|
50
7
|
refine YAML.singleton_class do
|
data/lib/spellr/base_reporter.rb
CHANGED
@@ -46,5 +46,13 @@ module Spellr
|
|
46
46
|
def counts
|
47
47
|
output.counts
|
48
48
|
end
|
49
|
+
|
50
|
+
def print_count(stat, noun, verb = stat, hide_zero: false)
|
51
|
+
print_value(counts[stat], noun, verb, hide_zero: hide_zero)
|
52
|
+
end
|
53
|
+
|
54
|
+
def print_value(value, noun, verb, hide_zero: false)
|
55
|
+
puts "#{pluralize noun, value} #{verb}" if !hide_zero || value.positive?
|
56
|
+
end
|
49
57
|
end
|
50
58
|
end
|
data/lib/spellr/check.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require_relative '../spellr'
|
4
4
|
require_relative 'tokenizer'
|
5
5
|
require_relative 'string_format'
|
6
|
+
require_relative 'output_stubbed'
|
6
7
|
|
7
8
|
module Spellr
|
8
9
|
class Check
|
@@ -21,8 +22,10 @@ module Spellr
|
|
21
22
|
end
|
22
23
|
|
23
24
|
def check
|
24
|
-
|
25
|
-
|
25
|
+
if Spellr.config.parallel
|
26
|
+
parallel_check
|
27
|
+
else
|
28
|
+
files.each { |file| check_and_count_file(file, reporter) }
|
26
29
|
end
|
27
30
|
|
28
31
|
reporter.finish
|
@@ -30,24 +33,48 @@ module Spellr
|
|
30
33
|
|
31
34
|
private
|
32
35
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
+
def parallel_check
|
37
|
+
require 'parallel'
|
38
|
+
|
39
|
+
Parallel.each(files, finish: ->(_, _, result) { reporter.output << result }) do |file|
|
40
|
+
sub_reporter = reporter.class.new(Spellr::OutputStubbed.new)
|
41
|
+
check_and_count_file(file, sub_reporter)
|
42
|
+
sub_reporter.output
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def check_and_count_file(file, current_reporter)
|
47
|
+
check_file(file, current_reporter)
|
48
|
+
current_reporter.output.increment(:checked)
|
36
49
|
rescue Spellr::InvalidByteSequence
|
37
50
|
# sometimes files are binary
|
38
|
-
|
51
|
+
current_reporter.warn "Skipped unreadable file: #{aqua file.relative_path}"
|
39
52
|
end
|
40
53
|
|
41
|
-
def check_file(file, start_at = nil,
|
54
|
+
def check_file(file, curr_reporter, start_at = nil, wordlist_proc = wordlist_proc_for(file))
|
55
|
+
restart_token = catch(:check_file_from) do
|
56
|
+
report_file(file, curr_reporter, start_at, wordlist_proc)
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
check_file_from_restart(file, curr_reporter, restart_token, wordlist_proc) if restart_token
|
60
|
+
end
|
61
|
+
|
62
|
+
def report_file(file, curr_reporter, start_at = nil, wordlist_proc = wordlist_proc_for(file))
|
42
63
|
Spellr::Tokenizer.new(file, start_at: start_at)
|
43
|
-
.each_token(skip_term_proc:
|
44
|
-
|
45
|
-
|
64
|
+
.each_token(skip_term_proc: wordlist_proc) do |token|
|
65
|
+
curr_reporter.call(token)
|
66
|
+
curr_reporter.output.exit_code = 1
|
46
67
|
end
|
47
68
|
end
|
48
69
|
|
70
|
+
def check_file_from_restart(file, current_reporter, restart_token, wordlist_proc)
|
71
|
+
# new wordlist cache when adding a word
|
72
|
+
wordlist_proc = wordlist_proc_for(file) unless restart_token.replacement
|
73
|
+
check_file(file, current_reporter, restart_token.location, wordlist_proc)
|
74
|
+
end
|
75
|
+
|
49
76
|
def wordlist_proc_for(file)
|
50
|
-
wordlists =
|
77
|
+
wordlists = file.wordlists
|
51
78
|
|
52
79
|
->(term) { wordlists.any? { |w| w.include?(term) } }
|
53
80
|
end
|
data/lib/spellr/cli_options.rb
CHANGED
@@ -9,8 +9,6 @@ module Spellr
|
|
9
9
|
class Options
|
10
10
|
class << self
|
11
11
|
def parse(argv)
|
12
|
-
@parallel_option = false
|
13
|
-
|
14
12
|
options.parse!(argv)
|
15
13
|
end
|
16
14
|
|
@@ -25,6 +23,7 @@ module Spellr
|
|
25
23
|
opts.on('-w', '--wordlist', 'Outputs errors in wordlist format', &method(:wordlist_option))
|
26
24
|
opts.on('-q', '--quiet', 'Silences output', &method(:quiet_option))
|
27
25
|
opts.on('-i', '--interactive', 'Runs the spell check interactively', &method(:interactive_option))
|
26
|
+
opts.on('-a', '--autocorrect', 'Autocorrect errors', &method(:autocorrect_option))
|
28
27
|
opts.separator('')
|
29
28
|
opts.on('--[no-]parallel', 'Run in parallel or not, default --parallel', &method(:parallel_option))
|
30
29
|
opts.on('-d', '--dry-run', 'List files to be checked', &method(:dry_run_option))
|
@@ -54,9 +53,12 @@ module Spellr
|
|
54
53
|
|
55
54
|
def interactive_option(_)
|
56
55
|
require_relative 'interactive'
|
57
|
-
require_relative 'check_interactive'
|
58
56
|
Spellr.config.reporter = Spellr::Interactive.new
|
59
|
-
|
57
|
+
end
|
58
|
+
|
59
|
+
def autocorrect_option(_)
|
60
|
+
require_relative 'autocorrect_reporter'
|
61
|
+
Spellr.config.reporter = Spellr::AutocorrectReporter.new
|
60
62
|
end
|
61
63
|
|
62
64
|
def suppress_file_rules(_)
|
@@ -73,15 +75,8 @@ module Spellr
|
|
73
75
|
Spellr.config.config_file = file
|
74
76
|
end
|
75
77
|
|
76
|
-
def parallel_option(parallel)
|
77
|
-
|
78
|
-
Spellr.config.checker = if parallel
|
79
|
-
require_relative 'check_parallel'
|
80
|
-
Spellr::CheckParallel
|
81
|
-
else
|
82
|
-
require_relative 'check'
|
83
|
-
Spellr::Check
|
84
|
-
end
|
78
|
+
def parallel_option(parallel)
|
79
|
+
Spellr.config.parallel = parallel
|
85
80
|
end
|
86
81
|
|
87
82
|
def dry_run_option(_)
|
data/lib/spellr/config.rb
CHANGED
@@ -10,7 +10,7 @@ require 'pathname'
|
|
10
10
|
|
11
11
|
module Spellr
|
12
12
|
class Config
|
13
|
-
attr_writer :reporter, :
|
13
|
+
attr_writer :reporter, :parallel
|
14
14
|
|
15
15
|
attr_accessor :suppress_file_rules, :dry_run
|
16
16
|
|
@@ -68,6 +68,10 @@ module Spellr
|
|
68
68
|
@reporter ||= default_reporter
|
69
69
|
end
|
70
70
|
|
71
|
+
def parallel
|
72
|
+
defined?(@parallel) ? @parallel : default_parallel
|
73
|
+
end
|
74
|
+
|
71
75
|
def checker
|
72
76
|
return dry_run_checker if dry_run?
|
73
77
|
|
@@ -101,8 +105,12 @@ module Spellr
|
|
101
105
|
end
|
102
106
|
|
103
107
|
def default_checker
|
104
|
-
require_relative '
|
105
|
-
Spellr::
|
108
|
+
require_relative 'check'
|
109
|
+
Spellr::Check
|
110
|
+
end
|
111
|
+
|
112
|
+
def default_parallel
|
113
|
+
reporter.class.name != 'Spellr::Interactive'
|
106
114
|
end
|
107
115
|
end
|
108
116
|
end
|
@@ -8,7 +8,7 @@ module Spellr
|
|
8
8
|
class ConfigValidator
|
9
9
|
include Spellr::Validations
|
10
10
|
|
11
|
-
validate :
|
11
|
+
validate :not_interactive_and_parallel
|
12
12
|
validate :interactive_is_interactive
|
13
13
|
validate :only_has_one_key_per_language
|
14
14
|
validate :languages_with_conflicting_keys
|
@@ -31,11 +31,11 @@ module Spellr
|
|
31
31
|
nil
|
32
32
|
end
|
33
33
|
|
34
|
-
def
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
34
|
+
def not_interactive_and_parallel
|
35
|
+
return unless Spellr.config.reporter.class.name == 'Spellr::Interactive' &&
|
36
|
+
Spellr.config.parallel
|
37
|
+
|
38
|
+
errors << 'CLI error: --interactive is incompatible with --parallel'
|
39
39
|
end
|
40
40
|
|
41
41
|
def only_has_one_key_per_language
|
data/lib/spellr/file.rb
CHANGED
data/lib/spellr/file_list.rb
CHANGED
data/lib/spellr/interactive.rb
CHANGED
@@ -5,18 +5,17 @@ require_relative '../spellr'
|
|
5
5
|
require_relative 'interactive_add'
|
6
6
|
require_relative 'interactive_replacement'
|
7
7
|
require_relative 'base_reporter'
|
8
|
+
require_relative 'suggester'
|
8
9
|
|
9
10
|
module Spellr
|
10
11
|
class Interactive < BaseReporter # rubocop:disable Metrics/ClassLength
|
11
|
-
def finish
|
12
|
+
def finish
|
12
13
|
puts "\n"
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
puts "#{pluralize 'error', counts[:total_fixed]} fixed" if counts[:total_fixed].positive?
|
19
|
-
puts "#{pluralize 'word', counts[:total_added]} added" if counts[:total_added].positive?
|
14
|
+
print_count(:checked, 'file')
|
15
|
+
print_value(total, 'error', 'found')
|
16
|
+
print_count(:total_skipped, 'error', 'skipped', hide_zero: true)
|
17
|
+
print_count(:total_fixed, 'error', 'fixed', hide_zero: true)
|
18
|
+
print_count(:total_added, 'word', 'added', hide_zero: true)
|
20
19
|
end
|
21
20
|
|
22
21
|
def global_replacements
|
@@ -27,22 +26,25 @@ module Spellr
|
|
27
26
|
@global_skips ||= counts[:global_skips] = []
|
28
27
|
end
|
29
28
|
|
30
|
-
def call(token)
|
29
|
+
def call(token, only_prompt: false)
|
31
30
|
# if attempt_global_replacement succeeds, then it throws,
|
32
31
|
# it acts like a guard clause all by itself.
|
33
32
|
attempt_global_replacement(token)
|
34
33
|
return if attempt_global_skip(token)
|
35
34
|
|
36
|
-
super
|
35
|
+
super(token) unless only_prompt
|
36
|
+
|
37
|
+
suggestions = ::Spellr::Suggester.fast_suggestions(token)
|
38
|
+
print_suggestions(suggestions) unless only_prompt
|
37
39
|
|
38
|
-
prompt(token)
|
40
|
+
prompt(token, suggestions)
|
39
41
|
end
|
40
42
|
|
41
43
|
def prompt_for_key
|
42
44
|
print "[ ]\e[2D"
|
43
45
|
end
|
44
46
|
|
45
|
-
def loop_within(seconds)
|
47
|
+
def loop_within(seconds)
|
46
48
|
# timeout is just because it gets stuck sometimes
|
47
49
|
Timeout.timeout(seconds * 10) do
|
48
50
|
start_time = monotonic_time
|
@@ -84,11 +86,21 @@ module Spellr
|
|
84
86
|
"^#{char.tr(CTRL_STR, ALPHABET)}"
|
85
87
|
end
|
86
88
|
|
87
|
-
def
|
89
|
+
def print_suggestions(suggestions)
|
90
|
+
return if suggestions.empty?
|
91
|
+
|
92
|
+
puts "Did you mean: #{number_suggestions(suggestions)}"
|
93
|
+
end
|
94
|
+
|
95
|
+
def number_suggestions(suggestions)
|
96
|
+
suggestions.map.with_index(1) { |word, i| "#{key i.to_s} #{word}" }.join(', ')
|
97
|
+
end
|
98
|
+
|
99
|
+
def prompt(token, suggestions)
|
88
100
|
print "#{key 'add'}, #{key 'replace'}, #{key 'skip'}, #{key 'help'}, [^#{bold 'C'}] to exit: "
|
89
101
|
prompt_for_key
|
90
102
|
|
91
|
-
handle_response(token)
|
103
|
+
handle_response(token, suggestions)
|
92
104
|
end
|
93
105
|
|
94
106
|
def clear_line(lines = 1)
|
@@ -121,9 +133,17 @@ module Spellr
|
|
121
133
|
throw :check_file_from, token
|
122
134
|
end
|
123
135
|
|
124
|
-
def
|
136
|
+
def suggestions_options(suggestions)
|
137
|
+
return suggestions if suggestions.empty?
|
138
|
+
|
139
|
+
('1'..(suggestions.length.to_s)).to_a
|
140
|
+
end
|
141
|
+
|
142
|
+
def handle_response(token, suggestions) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/AbcSize
|
143
|
+
numbers = suggestions_options(suggestions)
|
125
144
|
# :nocov:
|
126
|
-
|
145
|
+
letter = stdin_getch("qaAsSrR?h\u0003\u0004#{numbers.join}")
|
146
|
+
case letter
|
127
147
|
# :nocov:
|
128
148
|
when 'q', "\u0003" # ctrl c
|
129
149
|
Spellr.exit 1
|
@@ -137,20 +157,37 @@ module Spellr
|
|
137
157
|
Spellr::InteractiveReplacement.new(token, self).global_replace
|
138
158
|
when 'r'
|
139
159
|
Spellr::InteractiveReplacement.new(token, self).replace
|
160
|
+
when *numbers
|
161
|
+
handle_replace_with_suggestion(token, suggestions, letter)
|
140
162
|
when '?', 'h'
|
141
|
-
handle_help(token)
|
163
|
+
handle_help(token, suggestions)
|
142
164
|
end
|
143
165
|
end
|
144
166
|
|
167
|
+
def handle_replace_with_suggestion(token, suggestions, letter)
|
168
|
+
replacement = suggestions[letter.to_i - 1].chomp
|
169
|
+
|
170
|
+
token.replace(replacement)
|
171
|
+
increment(:total_fixed)
|
172
|
+
puts "Replaced #{red(token)} with #{green(replacement)}"
|
173
|
+
throw :check_file_from, token
|
174
|
+
end
|
175
|
+
|
145
176
|
def handle_skip(token)
|
146
177
|
increment(:total_skipped)
|
147
178
|
yield token if block_given?
|
148
179
|
puts "Skipped #{red(token)}"
|
149
180
|
end
|
150
181
|
|
151
|
-
def handle_help(token) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
182
|
+
def handle_help(token, suggestions) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
152
183
|
clear_line(2)
|
153
184
|
puts ''
|
185
|
+
if suggestions.length > 1
|
186
|
+
puts "#{key '1'}...#{key suggestions.length.to_s} "\
|
187
|
+
"Replace #{red token} with the numbered suggestion"
|
188
|
+
elsif suggestions.length == 1
|
189
|
+
puts "#{key '1'} Replace #{red token} with the numbered suggestion"
|
190
|
+
end
|
154
191
|
puts "#{key 'a'} Add #{red token} to a word list"
|
155
192
|
puts "#{key 'r'} Replace #{red token}"
|
156
193
|
puts "#{key 'R'} Replace this and all future instances of #{red token}"
|
@@ -160,7 +197,7 @@ module Spellr
|
|
160
197
|
puts "[ctrl] + #{key 'C'} Exit spellr"
|
161
198
|
puts ''
|
162
199
|
print "What do you want to do? [ ]\e[2D"
|
163
|
-
handle_response(token)
|
200
|
+
handle_response(token, suggestions)
|
164
201
|
end
|
165
202
|
end
|
166
203
|
end
|
@@ -79,7 +79,7 @@ class PossibleKey # rubocop:disable Metrics/ClassLength
|
|
79
79
|
letter_frequency_difference.slice(*FEATURE_LETTERS)
|
80
80
|
end
|
81
81
|
|
82
|
-
def character_set
|
82
|
+
def character_set
|
83
83
|
@character_set ||= case string
|
84
84
|
when /^[a-fA-F0-9\-]+$/ then :hex
|
85
85
|
when /^[a-z0-9]+$/ then :lower36
|
data/lib/spellr/reporter.rb
CHANGED
@@ -7,8 +7,8 @@ module Spellr
|
|
7
7
|
class Reporter < Spellr::BaseReporter
|
8
8
|
def finish
|
9
9
|
puts "\n"
|
10
|
-
|
11
|
-
|
10
|
+
print_count(:checked, 'file')
|
11
|
+
print_count(:total, 'error', 'found')
|
12
12
|
|
13
13
|
interactive_command if counts[:total].positive?
|
14
14
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'did_you_mean'
|
4
|
+
require 'jaro_winkler'
|
5
|
+
|
6
|
+
::DidYouMean.send(:remove_const, :JaroWinkler)
|
7
|
+
::DidYouMean::JaroWinkler = ::JaroWinkler
|
8
|
+
|
9
|
+
module Spellr
|
10
|
+
class Suggester
|
11
|
+
class << self
|
12
|
+
def suggestions(token)
|
13
|
+
wordlists = token.location.file.wordlists
|
14
|
+
term = token.spellr_normalize.chomp
|
15
|
+
words = wordlists.flat_map { |wordlist| wordlist.suggestions(token) }.uniq
|
16
|
+
words = ::DidYouMean::SpellChecker.new(dictionary: words).correct(term)
|
17
|
+
words = reduce_suggestions(words, term)
|
18
|
+
|
19
|
+
words.map { |word| word.send(token.case_method) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def slow?
|
23
|
+
return @slow if defined?(@slow)
|
24
|
+
|
25
|
+
@slow = ::JaroWinkler.method(:distance).source_location
|
26
|
+
end
|
27
|
+
|
28
|
+
def fast_suggestions(token)
|
29
|
+
if slow?
|
30
|
+
[]
|
31
|
+
else
|
32
|
+
suggestions(token)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def reduce_suggestions(words, term)
|
39
|
+
return words unless words.length > 1
|
40
|
+
|
41
|
+
threshold = ::JaroWinkler.distance(term, words.first) * 0.98
|
42
|
+
words.select! { |word| ::JaroWinkler.distance(term, word) > threshold }
|
43
|
+
words.sort_by! { |word| [-::JaroWinkler.distance(term, word), word] }
|
44
|
+
words.take(5)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize(wordlist)
|
49
|
+
@did_you_mean = ::DidYouMean::SpellChecker.new(dictionary: wordlist.to_a)
|
50
|
+
@suggestions = {}
|
51
|
+
end
|
52
|
+
|
53
|
+
def suggestions(term)
|
54
|
+
term = term.spellr_normalize
|
55
|
+
@suggestions.fetch(term) do
|
56
|
+
@suggestions[term] = @did_you_mean.correct(term).map(&:chomp)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/spellr/token.rb
CHANGED
@@ -66,5 +66,14 @@ module Spellr
|
|
66
66
|
@replacement = replacement
|
67
67
|
location.file.insert(replacement, file_char_range)
|
68
68
|
end
|
69
|
+
|
70
|
+
def case_method
|
71
|
+
@case_method ||= case self
|
72
|
+
when /\A[[:lower:]]+\z/ then :downcase
|
73
|
+
when /\A[[:upper:]]+\z/ then :upcase
|
74
|
+
when /\A[[:upper:]][[:lower:]]*\z/ then :capitalize
|
75
|
+
else :itself
|
76
|
+
end
|
77
|
+
end
|
69
78
|
end
|
70
79
|
end
|
data/lib/spellr/tokenizer.rb
CHANGED
@@ -34,7 +34,7 @@ module Spellr
|
|
34
34
|
file.close
|
35
35
|
end
|
36
36
|
|
37
|
-
def each_token(skip_term_proc: nil)
|
37
|
+
def each_token(skip_term_proc: nil)
|
38
38
|
each_line_with_stats do |line, line_number, char_offset, byte_offset|
|
39
39
|
prepare_tokenizer_for_line(line)&.each_token(skip_term_proc: skip_term_proc) do |token|
|
40
40
|
token.line = prepare_line(line, line_number, char_offset, byte_offset)
|
data/lib/spellr/version.rb
CHANGED
data/lib/spellr/wordlist.rb
CHANGED
@@ -78,8 +78,20 @@ module Spellr
|
|
78
78
|
to_a.length
|
79
79
|
end
|
80
80
|
|
81
|
+
def suggestions(term)
|
82
|
+
suggester.suggestions(term)
|
83
|
+
end
|
84
|
+
|
81
85
|
private
|
82
86
|
|
87
|
+
def suggester
|
88
|
+
@suggester ||= begin
|
89
|
+
require_relative 'suggester'
|
90
|
+
|
91
|
+
::Spellr::Suggester.new(self)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
83
95
|
def insert_sorted(term)
|
84
96
|
insert_at = words.bsearch_index { |value| value >= term }
|
85
97
|
insert_at ? words.insert(insert_at, term) : words.push(term)
|
@@ -87,6 +99,7 @@ module Spellr
|
|
87
99
|
|
88
100
|
def clear_cache
|
89
101
|
@words = nil
|
102
|
+
@suggester = nil
|
90
103
|
@include = {}
|
91
104
|
remove_instance_variable(:@exist) if defined?(@exist)
|
92
105
|
end
|
data/spellr.gemspec
CHANGED
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/main/CHANGELOG.md"
|
21
21
|
end
|
22
22
|
|
23
|
-
spec.required_ruby_version = '>= 2.
|
23
|
+
spec.required_ruby_version = '>= 2.5'
|
24
24
|
|
25
25
|
spec.files = Dir.glob('{lib,exe,wordlists}/**/{*,.*}') + %w{
|
26
26
|
CHANGELOG.md
|
@@ -47,6 +47,8 @@ Gem::Specification.new do |spec|
|
|
47
47
|
spec.add_development_dependency 'tty_string', '>= 1.1.0'
|
48
48
|
spec.add_development_dependency 'webmock', '~> 3.8'
|
49
49
|
|
50
|
+
spec.add_dependency 'did_you_mean'
|
50
51
|
spec.add_dependency 'fast_ignore', '>= 0.11.0'
|
52
|
+
spec.add_dependency 'jaro_winkler'
|
51
53
|
spec.add_dependency 'parallel', '~> 1.0'
|
52
54
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spellr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dana Sherson
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-02-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -192,6 +192,20 @@ dependencies:
|
|
192
192
|
- - "~>"
|
193
193
|
- !ruby/object:Gem::Version
|
194
194
|
version: '3.8'
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: did_you_mean
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - ">="
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: '0'
|
202
|
+
type: :runtime
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - ">="
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0'
|
195
209
|
- !ruby/object:Gem::Dependency
|
196
210
|
name: fast_ignore
|
197
211
|
requirement: !ruby/object:Gem::Requirement
|
@@ -206,6 +220,20 @@ dependencies:
|
|
206
220
|
- - ">="
|
207
221
|
- !ruby/object:Gem::Version
|
208
222
|
version: 0.11.0
|
223
|
+
- !ruby/object:Gem::Dependency
|
224
|
+
name: jaro_winkler
|
225
|
+
requirement: !ruby/object:Gem::Requirement
|
226
|
+
requirements:
|
227
|
+
- - ">="
|
228
|
+
- !ruby/object:Gem::Version
|
229
|
+
version: '0'
|
230
|
+
type: :runtime
|
231
|
+
prerelease: false
|
232
|
+
version_requirements: !ruby/object:Gem::Requirement
|
233
|
+
requirements:
|
234
|
+
- - ">="
|
235
|
+
- !ruby/object:Gem::Version
|
236
|
+
version: '0'
|
209
237
|
- !ruby/object:Gem::Dependency
|
210
238
|
name: parallel
|
211
239
|
requirement: !ruby/object:Gem::Requirement
|
@@ -235,12 +263,11 @@ files:
|
|
235
263
|
- exe/spellr
|
236
264
|
- lib/.spellr.yml
|
237
265
|
- lib/spellr.rb
|
266
|
+
- lib/spellr/autocorrect_reporter.rb
|
238
267
|
- lib/spellr/backports.rb
|
239
268
|
- lib/spellr/base_reporter.rb
|
240
269
|
- lib/spellr/check.rb
|
241
270
|
- lib/spellr/check_dry_run.rb
|
242
|
-
- lib/spellr/check_interactive.rb
|
243
|
-
- lib/spellr/check_parallel.rb
|
244
271
|
- lib/spellr/cli.rb
|
245
272
|
- lib/spellr/cli_options.rb
|
246
273
|
- lib/spellr/column_location.rb
|
@@ -266,6 +293,7 @@ files:
|
|
266
293
|
- lib/spellr/rake_task.rb
|
267
294
|
- lib/spellr/reporter.rb
|
268
295
|
- lib/spellr/string_format.rb
|
296
|
+
- lib/spellr/suggester.rb
|
269
297
|
- lib/spellr/token.rb
|
270
298
|
- lib/spellr/token_regexps.rb
|
271
299
|
- lib/spellr/tokenizer.rb
|
@@ -311,14 +339,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
311
339
|
requirements:
|
312
340
|
- - ">="
|
313
341
|
- !ruby/object:Gem::Version
|
314
|
-
version: '2.
|
342
|
+
version: '2.5'
|
315
343
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
316
344
|
requirements:
|
317
345
|
- - ">="
|
318
346
|
- !ruby/object:Gem::Version
|
319
347
|
version: '0'
|
320
348
|
requirements: []
|
321
|
-
rubygems_version: 3.
|
349
|
+
rubygems_version: 3.1.6
|
322
350
|
signing_key:
|
323
351
|
specification_version: 4
|
324
352
|
summary: Spell check your source code
|
@@ -1,24 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../spellr'
|
4
|
-
require_relative 'check'
|
5
|
-
|
6
|
-
module Spellr
|
7
|
-
class CheckInteractive < Check
|
8
|
-
private
|
9
|
-
|
10
|
-
def check_file_from_restart(file, restart_token, wordlist_proc)
|
11
|
-
# new wordlist cache when adding a word
|
12
|
-
wordlist_proc = wordlist_proc_for(file) unless restart_token.replacement
|
13
|
-
check_file(file, restart_token.location, wordlist_proc)
|
14
|
-
end
|
15
|
-
|
16
|
-
def check_file(file, start_at = nil, wordlist_proc = wordlist_proc_for(file))
|
17
|
-
restart_token = catch(:check_file_from) do
|
18
|
-
super(file, start_at, wordlist_proc)
|
19
|
-
nil
|
20
|
-
end
|
21
|
-
check_file_from_restart(file, restart_token, wordlist_proc) if restart_token
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
@@ -1,23 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../spellr'
|
4
|
-
require_relative 'check'
|
5
|
-
require_relative 'output_stubbed'
|
6
|
-
require 'parallel'
|
7
|
-
|
8
|
-
module Spellr
|
9
|
-
class CheckParallel < Check
|
10
|
-
def check # rubocop:disable Metrics/MethodLength
|
11
|
-
acc_reporter = @reporter
|
12
|
-
|
13
|
-
Parallel.each(files, finish: ->(_, _, result) { acc_reporter.output << result }) do |file|
|
14
|
-
@reporter = acc_reporter.class.new(Spellr::OutputStubbed.new)
|
15
|
-
check_and_count_file(file)
|
16
|
-
reporter.output
|
17
|
-
end
|
18
|
-
@reporter = acc_reporter
|
19
|
-
|
20
|
-
reporter.finish
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|