spellr 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|