spellr 0.6.0 → 0.7.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 +7 -0
- data/README.md +34 -15
- data/exe/spellr +8 -1
- data/lib/spellr/backports.rb +48 -21
- data/lib/spellr/base_reporter.rb +0 -4
- data/lib/spellr/check.rb +7 -47
- data/lib/spellr/check_dry_run.rb +14 -0
- data/lib/spellr/check_interactive.rb +24 -0
- data/lib/spellr/check_parallel.rb +23 -0
- data/lib/spellr/cli.rb +5 -90
- data/lib/spellr/cli_options.rb +98 -0
- data/lib/spellr/column_location.rb +2 -4
- data/lib/spellr/config.rb +39 -41
- data/lib/spellr/config_loader.rb +3 -29
- data/lib/spellr/config_validator.rb +63 -0
- data/lib/spellr/file_list.rb +7 -24
- data/lib/spellr/interactive.rb +80 -45
- data/lib/spellr/interactive_add.rb +20 -16
- data/lib/spellr/interactive_replacement.rb +52 -29
- data/lib/spellr/key_tuner/naive_bayes.rb +7 -59
- data/lib/spellr/key_tuner/possible_key.rb +5 -24
- data/lib/spellr/key_tuner/stats.rb +2 -0
- data/lib/spellr/language.rb +7 -8
- data/lib/spellr/line_location.rb +2 -7
- data/lib/spellr/line_tokenizer.rb +1 -9
- data/lib/spellr/output.rb +5 -7
- data/lib/spellr/output_stubbed.rb +16 -16
- data/lib/spellr/quiet_reporter.rb +1 -0
- data/lib/spellr/reporter.rb +0 -4
- data/lib/spellr/string_format.rb +8 -15
- data/lib/spellr/token.rb +13 -29
- data/lib/spellr/token_regexps.rb +2 -2
- data/lib/spellr/tokenizer.rb +0 -10
- data/lib/spellr/validations.rb +31 -0
- data/lib/spellr/version.rb +1 -1
- data/lib/spellr/wordlist.rb +9 -17
- data/lib/spellr/wordlist_reporter.rb +0 -4
- data/lib/spellr.rb +12 -0
- data/spellr.gemspec +12 -7
- data/wordlists/ruby.txt +1 -0
- metadata +59 -26
data/lib/spellr/config.rb
CHANGED
@@ -3,38 +3,22 @@
|
|
3
3
|
require_relative '../spellr'
|
4
4
|
require_relative 'config_loader'
|
5
5
|
require_relative 'language'
|
6
|
-
require_relative '
|
6
|
+
require_relative 'config_validator'
|
7
7
|
require 'pathname'
|
8
8
|
|
9
9
|
module Spellr
|
10
10
|
class Config
|
11
11
|
attr_writer :reporter
|
12
|
+
attr_writer :checker
|
12
13
|
attr_reader :config_file
|
13
14
|
attr_accessor :quiet
|
14
15
|
alias_method :quiet?, :quiet
|
16
|
+
attr_accessor :dry_run
|
15
17
|
|
16
18
|
def initialize
|
17
19
|
@config = ConfigLoader.new
|
18
20
|
end
|
19
21
|
|
20
|
-
def valid?
|
21
|
-
only_has_one_key_per_language
|
22
|
-
keys_are_single_characters
|
23
|
-
errors.empty?
|
24
|
-
end
|
25
|
-
|
26
|
-
def print_errors
|
27
|
-
if $stderr.tty?
|
28
|
-
errors.each { |e| warn "\e[31m#{e}\e[0m" }
|
29
|
-
else
|
30
|
-
errors.each { |e| warn e }
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def errors
|
35
|
-
@errors ||= []
|
36
|
-
end
|
37
|
-
|
38
22
|
def word_minimum_length
|
39
23
|
@word_minimum_length ||= @config[:word_minimum_length]
|
40
24
|
end
|
@@ -55,10 +39,6 @@ module Spellr
|
|
55
39
|
@excludes ||= @config[:excludes] || []
|
56
40
|
end
|
57
41
|
|
58
|
-
def color
|
59
|
-
@config[:color]
|
60
|
-
end
|
61
|
-
|
62
42
|
def languages
|
63
43
|
@languages ||= @config[:languages].map do |key, args|
|
64
44
|
Spellr::Language.new(key, **args)
|
@@ -66,7 +46,11 @@ module Spellr
|
|
66
46
|
end
|
67
47
|
|
68
48
|
def pwd
|
69
|
-
@pwd ||= Pathname.pwd
|
49
|
+
@pwd ||= Pathname.new(ENV['SPELLR_TEST_PWD'] || Dir.pwd)
|
50
|
+
end
|
51
|
+
|
52
|
+
def pwd_s
|
53
|
+
@pwd_s ||= pwd.to_s
|
70
54
|
end
|
71
55
|
|
72
56
|
def languages_for(file)
|
@@ -78,7 +62,7 @@ module Spellr
|
|
78
62
|
end
|
79
63
|
|
80
64
|
def config_file=(value)
|
81
|
-
|
65
|
+
reset!
|
82
66
|
@config = ConfigLoader.new(value)
|
83
67
|
end
|
84
68
|
|
@@ -86,32 +70,46 @@ module Spellr
|
|
86
70
|
@reporter ||= default_reporter
|
87
71
|
end
|
88
72
|
|
89
|
-
|
73
|
+
def checker
|
74
|
+
return dry_run_checker if @dry_run
|
90
75
|
|
91
|
-
|
92
|
-
languages_with_conflicting_keys.each do |conflicts|
|
93
|
-
errors << "Error: #{conflicts.map(&:name).join(' & ')} share the same language key "\
|
94
|
-
"(#{conflicts.first.key}). Please define one to be different with `key:`"
|
95
|
-
end
|
76
|
+
@checker ||= default_checker
|
96
77
|
end
|
97
78
|
|
98
|
-
def
|
99
|
-
|
100
|
-
g.length > 1
|
101
|
-
end
|
79
|
+
def valid?
|
80
|
+
Spellr::ConfigValidator.new.valid?
|
102
81
|
end
|
103
82
|
|
104
|
-
def
|
105
|
-
|
83
|
+
def reset! # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
|
84
|
+
@config = ConfigLoader.new
|
85
|
+
remove_instance_variable(:@languages) if defined?(@languages)
|
86
|
+
remove_instance_variable(:@excludes) if defined?(@excludes)
|
87
|
+
remove_instance_variable(:@includes) if defined?(@includes)
|
88
|
+
remove_instance_variable(:@word_minimum_length) if defined?(@word_minimum_length)
|
89
|
+
remove_instance_variable(:@key_heuristic_weight) if defined?(@key_heuristic_weight)
|
90
|
+
remove_instance_variable(:@key_minimum_length) if defined?(@key_minimum_length)
|
91
|
+
end
|
106
92
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
93
|
+
private
|
94
|
+
|
95
|
+
def dry_run_checker
|
96
|
+
require_relative 'check_dry_run'
|
97
|
+
Spellr::CheckDryRun
|
111
98
|
end
|
112
99
|
|
113
100
|
def default_reporter
|
101
|
+
require_relative 'reporter'
|
114
102
|
Spellr::Reporter.new
|
115
103
|
end
|
104
|
+
|
105
|
+
def default_checker
|
106
|
+
require_relative 'check_parallel'
|
107
|
+
Spellr::CheckParallel
|
108
|
+
end
|
109
|
+
|
110
|
+
def clear_pwd
|
111
|
+
remove_instance_variable(:@pwd) if defined?(@pwd)
|
112
|
+
remove_instance_variable(:@pwd_s) if defined?(@pwd_s)
|
113
|
+
end
|
116
114
|
end
|
117
115
|
end
|
data/lib/spellr/config_loader.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'yaml'
|
4
|
+
require_relative 'backports'
|
4
5
|
|
5
6
|
module Spellr
|
6
7
|
class ConfigLoader
|
@@ -10,15 +11,8 @@ module Spellr
|
|
10
11
|
@config_file = config_file
|
11
12
|
end
|
12
13
|
|
13
|
-
def config_file=(value)
|
14
|
-
# TODO: nicer error message
|
15
|
-
::File.read(value) # raise Errno::ENOENT if the file doesn't exist
|
16
|
-
@config_file = value
|
17
|
-
@config = nil
|
18
|
-
end
|
19
|
-
|
20
14
|
def [](value)
|
21
|
-
load_config unless @config
|
15
|
+
load_config unless defined?(@config)
|
22
16
|
@config[value]
|
23
17
|
end
|
24
18
|
|
@@ -34,27 +28,7 @@ module Spellr
|
|
34
28
|
def load_yaml(path)
|
35
29
|
return {} unless ::File.exist?(path)
|
36
30
|
|
37
|
-
|
38
|
-
end
|
39
|
-
|
40
|
-
def symbolize_yaml_safe_load(path)
|
41
|
-
if RUBY_VERSION >= '2.5'
|
42
|
-
YAML.safe_load(::File.read(path), symbolize_names: true)
|
43
|
-
else
|
44
|
-
symbolize_names!(YAML.safe_load(::File.read(path)))
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def symbolize_names!(obj) # rubocop:disable Metrics/MethodLength
|
49
|
-
case obj
|
50
|
-
when Hash
|
51
|
-
obj.keys.each do |key|
|
52
|
-
obj[key.to_sym] = symbolize_names!(obj.delete(key))
|
53
|
-
end
|
54
|
-
when Array
|
55
|
-
obj.map! { |ea| symbolize_names!(ea) }
|
56
|
-
end
|
57
|
-
obj
|
31
|
+
YAML.safe_load(::File.read(path), symbolize_names: true)
|
58
32
|
end
|
59
33
|
|
60
34
|
def merge_config(default, project) # rubocop:disable Metrics/MethodLength
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'validations'
|
4
|
+
require 'timeout'
|
5
|
+
require 'io/console'
|
6
|
+
|
7
|
+
module Spellr
|
8
|
+
class ConfigValidator
|
9
|
+
include Spellr::Validations
|
10
|
+
|
11
|
+
validate :checker_and_reporter_coexist
|
12
|
+
validate :interactive_is_interactive
|
13
|
+
validate :only_has_one_key_per_language
|
14
|
+
validate :languages_with_conflicting_keys
|
15
|
+
validate :keys_are_single_characters
|
16
|
+
|
17
|
+
def valid?
|
18
|
+
raise Spellr::Config::Invalid, errors.join("\n") unless super
|
19
|
+
end
|
20
|
+
|
21
|
+
def interactive_is_interactive # rubocop:disable Metrics/MethodLength
|
22
|
+
return unless Spellr.config.reporter.class.name == 'Spellr::Interactive'
|
23
|
+
|
24
|
+
# I have no idea how to check for this other than call it
|
25
|
+
Timeout.timeout(0.0000000000001) do
|
26
|
+
$stdin.getch
|
27
|
+
end
|
28
|
+
rescue Errno::ENOTTY, Errno::ENODEV
|
29
|
+
errors << 'CLI error: --interactive is unavailable in a non-interactive terminal'
|
30
|
+
rescue Timeout::Error
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def checker_and_reporter_coexist
|
35
|
+
if Spellr.config.reporter.class.name == 'Spellr::Interactive' &&
|
36
|
+
Spellr.config.checker.name == 'Spellr::CheckParallel'
|
37
|
+
errors << 'CLI error: --interactive is incompatible with --parallel'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def only_has_one_key_per_language
|
42
|
+
languages_with_conflicting_keys.each do |conflicts|
|
43
|
+
errors << "Config error: #{conflicts.map(&:name).join(' & ')} share the same language key "\
|
44
|
+
"(#{conflicts.first.key}). Please define one to be different with `key:`"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def languages_with_conflicting_keys
|
49
|
+
Spellr.config.languages.select(&:addable?).group_by(&:key).values.select do |g|
|
50
|
+
g.length > 1
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def keys_are_single_characters
|
55
|
+
bad_languages = Spellr.config.languages.select { |l| l.key.length > 1 }
|
56
|
+
|
57
|
+
bad_languages.each do |lang|
|
58
|
+
errors << "Config error: #{lang.name} defines a key that is too long (#{lang.key}). "\
|
59
|
+
'Please change it to be a single character'
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/spellr/file_list.rb
CHANGED
@@ -8,17 +8,15 @@ module Spellr
|
|
8
8
|
class FileList
|
9
9
|
include Enumerable
|
10
10
|
|
11
|
-
def initialize(
|
11
|
+
def initialize(patterns = nil)
|
12
12
|
@patterns = patterns
|
13
13
|
end
|
14
14
|
|
15
15
|
def each
|
16
|
-
|
17
|
-
next unless cli_patterns_ignore.allowed?(file)
|
18
|
-
|
19
|
-
file = Spellr::File.new(file)
|
16
|
+
return enum_for(:each) unless block_given?
|
20
17
|
|
21
|
-
|
18
|
+
fast_ignore.each do |file|
|
19
|
+
yield(Spellr::File.new(file))
|
22
20
|
end
|
23
21
|
end
|
24
22
|
|
@@ -28,28 +26,13 @@ module Spellr
|
|
28
26
|
|
29
27
|
private
|
30
28
|
|
31
|
-
|
32
|
-
def cli_patterns
|
33
|
-
@patterns.map do |pattern|
|
34
|
-
pattern.sub(%r{^(?!(?:[/~*]|\.{1,2}/))}, '/')
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def gitignore_path
|
39
|
-
gitignore = ::File.join(Dir.pwd, '.gitignore')
|
40
|
-
gitignore if ::File.exist?(gitignore)
|
41
|
-
end
|
42
|
-
|
43
|
-
def fast_ignore
|
29
|
+
def fast_ignore # rubocop:disable Metrics/MethodLength
|
44
30
|
FastIgnore.new(
|
45
31
|
ignore_rules: Spellr.config.excludes,
|
46
32
|
include_rules: Spellr.config.includes,
|
47
|
-
|
33
|
+
argv_rules: @patterns,
|
34
|
+
root: Spellr.config.pwd_s
|
48
35
|
)
|
49
36
|
end
|
50
|
-
|
51
|
-
def cli_patterns_ignore
|
52
|
-
@cli_patterns_ignore ||= FastIgnore.new(include_rules: cli_patterns, gitignore: false)
|
53
|
-
end
|
54
37
|
end
|
55
38
|
end
|
data/lib/spellr/interactive.rb
CHANGED
@@ -1,18 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'io/console'
|
4
|
-
require 'readline'
|
5
4
|
require_relative '../spellr'
|
6
5
|
require_relative 'interactive_add'
|
7
6
|
require_relative 'interactive_replacement'
|
8
7
|
require_relative 'base_reporter'
|
9
8
|
|
10
9
|
module Spellr
|
11
|
-
class Interactive < BaseReporter
|
12
|
-
def parallel?
|
13
|
-
false
|
14
|
-
end
|
15
|
-
|
10
|
+
class Interactive < BaseReporter # rubocop:disable Metrics/ClassLength
|
16
11
|
def finish # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
17
12
|
puts "\n"
|
18
13
|
puts "#{pluralize 'file', counts[:checked]} checked"
|
@@ -25,21 +20,17 @@ module Spellr
|
|
25
20
|
end
|
26
21
|
|
27
22
|
def global_replacements
|
28
|
-
@global_replacements ||=
|
29
|
-
counts[:global_replacements] = {} unless counts.key?(:global_replacements)
|
30
|
-
counts[:global_replacements]
|
31
|
-
end
|
23
|
+
@global_replacements ||= counts[:global_replacements] = {}
|
32
24
|
end
|
33
25
|
|
34
26
|
def global_skips
|
35
|
-
@global_skips ||=
|
36
|
-
counts[:global_skips] = [] unless counts.key?(:global_skips)
|
37
|
-
counts[:global_skips]
|
38
|
-
end
|
27
|
+
@global_skips ||= counts[:global_skips] = []
|
39
28
|
end
|
40
29
|
|
41
30
|
def call(token)
|
42
|
-
|
31
|
+
# if attempt_global_replacement succeeds, then it throws,
|
32
|
+
# it acts like a guard clause all by itself.
|
33
|
+
attempt_global_replacement(token)
|
43
34
|
return if attempt_global_skip(token)
|
44
35
|
|
45
36
|
super
|
@@ -47,24 +38,71 @@ module Spellr
|
|
47
38
|
prompt(token)
|
48
39
|
end
|
49
40
|
|
50
|
-
def
|
41
|
+
def prompt_for_key
|
42
|
+
print "[ ]\e[2D"
|
43
|
+
end
|
44
|
+
|
45
|
+
def loop_within(seconds) # rubocop:disable Metrics/MethodLength
|
46
|
+
# timeout is just because it gets stuck sometimes
|
47
|
+
Timeout.timeout(seconds * 10) do
|
48
|
+
start_time = monotonic_time
|
49
|
+
yield until start_time + seconds < monotonic_time
|
50
|
+
end
|
51
|
+
rescue Timeout::Error
|
52
|
+
# :nocov:
|
53
|
+
nil
|
54
|
+
# :nocov:
|
55
|
+
end
|
56
|
+
|
57
|
+
def monotonic_time
|
58
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
59
|
+
end
|
60
|
+
|
61
|
+
def stdin_getch(legal_chars) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
51
62
|
choice = output.stdin.getch
|
52
|
-
|
53
|
-
choice
|
63
|
+
|
64
|
+
if legal_chars.include?(choice)
|
65
|
+
puts "\e[0K#{bold print_keypress(choice)}]\e[1C"
|
66
|
+
choice
|
67
|
+
elsif choice == "\e" # mac sends \e[A when up is pressed. thanks.
|
68
|
+
print "\a"
|
69
|
+
loop_within(0.001) { output.stdin.getch }
|
70
|
+
|
71
|
+
stdin_getch(legal_chars)
|
72
|
+
else
|
73
|
+
print "\a"
|
74
|
+
stdin_getch(legal_chars)
|
75
|
+
end
|
54
76
|
end
|
55
77
|
|
56
|
-
|
78
|
+
ALPHABET = ('A'..'Z').to_a.join
|
79
|
+
CTRL = ("\u0001".."\u0026").freeze
|
80
|
+
CTRL_STR = CTRL.to_a.join
|
81
|
+
def print_keypress(char)
|
82
|
+
return char unless CTRL.cover?(char)
|
57
83
|
|
58
|
-
|
59
|
-
counts[:total_skipped] + counts[:total_fixed] + counts[:total_added]
|
84
|
+
"^#{char.tr(CTRL_STR, ALPHABET)}"
|
60
85
|
end
|
61
86
|
|
62
87
|
def prompt(token)
|
63
|
-
print
|
88
|
+
print "#{key 'add'}, #{key 'replace'}, #{key 'skip'}, #{key 'help'}, [^#{bold 'C'}] to exit: "
|
89
|
+
prompt_for_key
|
64
90
|
|
65
91
|
handle_response(token)
|
66
|
-
|
67
|
-
|
92
|
+
end
|
93
|
+
|
94
|
+
def clear_line(lines = 1)
|
95
|
+
print "\r\e[K"
|
96
|
+
(lines - 1).times do
|
97
|
+
sleep 0.01
|
98
|
+
print "\r\e[1T\e[2K"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def total
|
105
|
+
counts[:total_skipped] + counts[:total_fixed] + counts[:total_added]
|
68
106
|
end
|
69
107
|
|
70
108
|
def attempt_global_skip(token)
|
@@ -83,15 +121,13 @@ module Spellr
|
|
83
121
|
throw :check_file_from, token
|
84
122
|
end
|
85
123
|
|
86
|
-
def
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
case stdin_getch
|
92
|
-
when "\u0003" # ctrl c
|
124
|
+
def handle_response(token) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
125
|
+
# :nocov:
|
126
|
+
case stdin_getch("qaAsSrR?h\u0003\u0004")
|
127
|
+
# :nocov:
|
128
|
+
when 'q', "\u0003" # ctrl c
|
93
129
|
exit 1
|
94
|
-
when 'a'
|
130
|
+
when 'a', 'A'
|
95
131
|
Spellr::InteractiveAdd.new(token, self)
|
96
132
|
when 's', "\u0004" # ctrl d
|
97
133
|
handle_skip(token)
|
@@ -101,13 +137,8 @@ module Spellr
|
|
101
137
|
Spellr::InteractiveReplacement.new(token, self).global_replace
|
102
138
|
when 'r'
|
103
139
|
Spellr::InteractiveReplacement.new(token, self).replace
|
104
|
-
when '
|
105
|
-
Spellr::InteractiveReplacement.new(token, self).replace_line
|
106
|
-
when '?'
|
140
|
+
when '?', 'h'
|
107
141
|
handle_help(token)
|
108
|
-
else
|
109
|
-
clear_current_line
|
110
|
-
call(token)
|
111
142
|
end
|
112
143
|
end
|
113
144
|
|
@@ -118,13 +149,17 @@ module Spellr
|
|
118
149
|
end
|
119
150
|
|
120
151
|
def handle_help(token) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
121
|
-
|
122
|
-
puts
|
123
|
-
puts "#{
|
124
|
-
puts "#{
|
125
|
-
puts "#{
|
126
|
-
puts "#{
|
127
|
-
puts "#{
|
152
|
+
clear_line(2)
|
153
|
+
puts ''
|
154
|
+
puts "#{key 'a'} Add #{red token} to a word list"
|
155
|
+
puts "#{key 'r'} Replace #{red token}"
|
156
|
+
puts "#{key 'R'} Replace this and all future instances of #{red token}"
|
157
|
+
puts "#{key 's'} Skip #{red token}"
|
158
|
+
puts "#{key 'S'} Skip this and all future instances of #{red token}"
|
159
|
+
puts "#{key 'h'} Show this help"
|
160
|
+
puts "[ctrl] + #{key 'C'} Exit spellr"
|
161
|
+
puts ''
|
162
|
+
print "What do you want to do? [ ]\e[2D"
|
128
163
|
handle_response(token)
|
129
164
|
end
|
130
165
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative '../spellr'
|
3
4
|
require_relative 'string_format'
|
4
5
|
|
5
6
|
module Spellr
|
@@ -12,6 +13,7 @@ module Spellr
|
|
12
13
|
@token = token
|
13
14
|
@reporter = reporter
|
14
15
|
|
16
|
+
puts ''
|
15
17
|
ask_wordlist
|
16
18
|
end
|
17
19
|
|
@@ -28,39 +30,41 @@ module Spellr
|
|
28
30
|
end
|
29
31
|
|
30
32
|
def ask_wordlist
|
31
|
-
puts "
|
33
|
+
addable_languages.each { |l| puts " #{key l.key} #{l.name}" }
|
34
|
+
puts " [^#{bold 'C'}] to go back"
|
35
|
+
print " Add #{red(token)} to which wordlist? "
|
36
|
+
reporter.prompt_for_key
|
32
37
|
|
33
|
-
|
34
|
-
puts "[#{language.key}] #{language.name}"
|
35
|
-
end
|
36
|
-
|
37
|
-
handle_wordlist_choice(reporter.stdin_getch)
|
38
|
+
handle_wordlist_choice
|
38
39
|
end
|
39
40
|
|
40
41
|
def handle_ctrl_c
|
41
|
-
|
42
|
+
reporter.clear_line(language_keys.length + 6)
|
42
43
|
reporter.call(token)
|
43
44
|
end
|
44
45
|
|
45
|
-
def handle_wordlist_choice
|
46
|
+
def handle_wordlist_choice
|
47
|
+
choice = reporter.stdin_getch("#{language_keys.join}\u0003")
|
48
|
+
# :nocov:
|
46
49
|
case choice
|
47
|
-
|
48
|
-
|
49
|
-
when *language_keys
|
50
|
-
add_to_wordlist(choice)
|
51
|
-
else
|
52
|
-
ask_wordlist
|
50
|
+
# :nocov:
|
51
|
+
when "\u0003" then handle_ctrl_c
|
52
|
+
when *language_keys then add_to_wordlist(choice)
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
56
|
def add_to_wordlist(choice)
|
57
|
-
wordlist =
|
57
|
+
wordlist = find_wordlist(choice)
|
58
58
|
wordlist << token
|
59
59
|
reporter.increment(:total_added)
|
60
|
-
puts "
|
60
|
+
puts "\nAdded #{red(token)} to the #{bold wordlist.name} wordlist"
|
61
61
|
throw :check_file_from, token
|
62
62
|
end
|
63
63
|
|
64
|
+
def find_wordlist(key)
|
65
|
+
addable_languages.find { |w| w.key == key }.project_wordlist
|
66
|
+
end
|
67
|
+
|
64
68
|
def puts(str)
|
65
69
|
reporter.puts(str)
|
66
70
|
end
|
@@ -1,13 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'readline'
|
4
|
+
require_relative 'string_format'
|
5
|
+
|
3
6
|
module Spellr
|
4
7
|
class InteractiveReplacement
|
5
8
|
include Spellr::StringFormat
|
6
9
|
|
7
|
-
attr_reader :token, :reporter, :
|
10
|
+
attr_reader :token, :reporter, :token_highlight
|
11
|
+
attr_accessor :global
|
12
|
+
alias_method :global?, :global
|
8
13
|
|
9
14
|
def initialize(token, reporter)
|
10
|
-
@
|
15
|
+
@token = token
|
11
16
|
@token_highlight = red(token)
|
12
17
|
@reporter = reporter
|
13
18
|
Readline.input = reporter.output.stdin
|
@@ -15,51 +20,69 @@ module Spellr
|
|
15
20
|
end
|
16
21
|
|
17
22
|
def global_replace
|
18
|
-
|
19
|
-
end
|
20
|
-
|
21
|
-
def replace_line
|
22
|
-
@token = token.line
|
23
|
-
@token_highlight = token.highlight(original_token.char_range).chomp
|
24
|
-
@suffix = "\n"
|
25
|
-
|
23
|
+
self.global = true
|
26
24
|
replace
|
27
25
|
end
|
28
26
|
|
29
27
|
def complete_replacement(replacement)
|
30
|
-
|
28
|
+
handle_global_replacement(replacement)
|
29
|
+
token.replace(replacement)
|
31
30
|
|
32
31
|
reporter.increment(:total_fixed)
|
33
|
-
puts "
|
32
|
+
puts "\n\e[0mReplaced #{'all ' if global?}#{token_highlight} with #{green(replacement)}"
|
34
33
|
throw :check_file_from, token
|
35
34
|
end
|
36
35
|
|
37
|
-
def
|
38
|
-
|
36
|
+
def handle_global_replacement(replacement)
|
37
|
+
reporter.global_replacements[token.to_s] = replacement if global?
|
38
|
+
end
|
39
39
|
|
40
|
-
|
41
|
-
|
40
|
+
def ask_replacement
|
41
|
+
puts ''
|
42
|
+
puts " #{lighten '[^C] to go back'}"
|
43
|
+
prompt_replacement
|
44
|
+
end
|
45
|
+
|
46
|
+
def prompt_replacement
|
47
|
+
Readline.pre_input_hook = -> { pre_input_hook(token) }
|
48
|
+
prompt = " Replace #{'all ' if global?}#{token_highlight} with: \e[32m"
|
49
|
+
Readline.readline(prompt)
|
50
|
+
rescue Interrupt
|
51
|
+
handle_ctrl_c
|
52
|
+
end
|
42
53
|
|
43
|
-
|
54
|
+
def re_ask_replacement
|
55
|
+
print "\e[0m\a\e[1T"
|
56
|
+
|
57
|
+
try_replace(prompt_replacement)
|
58
|
+
end
|
59
|
+
|
60
|
+
def try_replace(replacement)
|
61
|
+
return re_ask_replacement if replacement == token
|
62
|
+
return re_ask_replacement if replacement.empty?
|
44
63
|
|
45
|
-
yield replacement if block_given?
|
46
64
|
complete_replacement(replacement)
|
47
|
-
|
48
|
-
|
49
|
-
|
65
|
+
end
|
66
|
+
|
67
|
+
def replace
|
68
|
+
try_replace(ask_replacement)
|
69
|
+
end
|
70
|
+
|
71
|
+
def handle_ctrl_c
|
72
|
+
print "\e[0m"
|
73
|
+
reporter.clear_line(5)
|
74
|
+
reporter.call(token)
|
50
75
|
end
|
51
76
|
|
52
77
|
private
|
53
78
|
|
54
|
-
def
|
55
|
-
Readline.
|
56
|
-
|
57
|
-
|
58
|
-
Readline.redisplay
|
79
|
+
def pre_input_hook(value)
|
80
|
+
Readline.refresh_line
|
81
|
+
Readline.insert_text value.to_s
|
82
|
+
Readline.redisplay
|
59
83
|
|
60
|
-
|
61
|
-
|
62
|
-
}
|
84
|
+
# Remove the hook right away.
|
85
|
+
Readline.pre_input_hook = nil
|
63
86
|
end
|
64
87
|
|
65
88
|
def puts(str)
|