spellr 0.5.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b12a7a52fc68f4960a9156f4d500366225de366b3ca20213231a22176c898030
4
- data.tar.gz: 816aafdee27ec4c997e96b3726f37f1e08dc22aaee9f29680aee9f86b8481693
3
+ metadata.gz: e2aaca19a62f3dafd26ff2948289fac3f12e83ca30660b2b17d5d97866b5c8c1
4
+ data.tar.gz: fe0b3266121b748b26f1425a70c5b9d5e25a9d31a5a4befe66a84be8c6d1a9ee
5
5
  SHA512:
6
- metadata.gz: 52e520722759e56c001ee0271408a7bb87e47cfb3ed1cba15ae1c208356a26cae1be01f05e72c316a6141e0e7e71aebe3878862de975cf12eedd77e64c2c1c3f
7
- data.tar.gz: 3f11b304e48cc73823dd66b23daec1622ab1ee0e94545929a15d0baf80a4fc616a3dfa8f2ec9dda28e9792701d277fb0dc8e3cbbf673531dc8aca3fe9947ffd0
6
+ metadata.gz: 3b23613dee23ae8a8195d39a2abe55190a4b1b400174c197469391cb18d5d282a99920f5e3b7468030d7aa078ec0fe5d33bcf8b726e65e82dddd55cc9ba9f396
7
+ data.tar.gz: 2ef4a3eb0f30574fc2b77564af37a0ef0fdc71b03c8377bab597d9fb81d978b49dda614366fe3cafe8d4d01d6e1f98307af7159391fc903c368cf1e0d8a3a493
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ # v0.5.1
2
+ - Check files in parallel
3
+ - Lots of pure refactoring
4
+ - Capfile is now considered a ruby file by default
5
+
1
6
  # v0.5.0
2
7
  - Removed the fetch command it was just unnecessarily slow and awkward. instead use `locale: [US,AU]`
3
8
  - Added usage documentation
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- spellr (0.5.0)
4
+ spellr (0.5.1)
5
5
  fast_ignore (~> 0.4.0)
6
6
 
7
7
  GEM
@@ -13,7 +13,7 @@ GEM
13
13
  fast_ignore (0.4.1)
14
14
  jaro_winkler (1.5.3)
15
15
  method_source (0.9.2)
16
- parallel (1.17.0)
16
+ parallel (1.18.0)
17
17
  parser (2.6.5.0)
18
18
  ast (~> 2.4.0)
19
19
  pry (0.12.2)
@@ -21,21 +21,21 @@ GEM
21
21
  method_source (~> 0.9.0)
22
22
  rainbow (3.0.0)
23
23
  rake (10.5.0)
24
- rspec (3.8.0)
25
- rspec-core (~> 3.8.0)
26
- rspec-expectations (~> 3.8.0)
27
- rspec-mocks (~> 3.8.0)
28
- rspec-core (3.8.2)
29
- rspec-support (~> 3.8.0)
24
+ rspec (3.9.0)
25
+ rspec-core (~> 3.9.0)
26
+ rspec-expectations (~> 3.9.0)
27
+ rspec-mocks (~> 3.9.0)
28
+ rspec-core (3.9.0)
29
+ rspec-support (~> 3.9.0)
30
30
  rspec-eventually (0.2.2)
31
- rspec-expectations (3.8.5)
31
+ rspec-expectations (3.9.0)
32
32
  diff-lcs (>= 1.2.0, < 2.0)
33
- rspec-support (~> 3.8.0)
34
- rspec-mocks (3.8.2)
33
+ rspec-support (~> 3.9.0)
34
+ rspec-mocks (3.9.0)
35
35
  diff-lcs (>= 1.2.0, < 2.0)
36
- rspec-support (~> 3.8.0)
37
- rspec-support (3.8.3)
38
- rubocop (0.75.0)
36
+ rspec-support (~> 3.9.0)
37
+ rspec-support (3.9.0)
38
+ rubocop (0.76.0)
39
39
  jaro_winkler (~> 1.5.1)
40
40
  parallel (~> 1.10)
41
41
  parser (>= 2.6)
data/lib/.spellr.yml CHANGED
@@ -41,8 +41,10 @@ languages:
41
41
  - Gemfile
42
42
  - Rakefile
43
43
  - config.ru
44
+ - Capfile
44
45
  hashbangs:
45
46
  - ruby
47
+
46
48
  html:
47
49
  includes:
48
50
  - '*.html'
@@ -3,12 +3,10 @@
3
3
  class Array
4
4
  unless RUBY_VERSION >= '2.4'
5
5
  def sum
6
- reduce(0) do |total, value|
7
- total + if block_given?
8
- yield value
9
- else
10
- value
11
- end
6
+ if block_given?
7
+ reduce(0) { |total, value| total + yield(value) }
8
+ else
9
+ reduce(:+)
12
10
  end
13
11
  end
14
12
  end
@@ -17,3 +15,15 @@ end
17
15
  class String
18
16
  alias_method :match?, :match unless RUBY_VERSION >= '2.4'
19
17
  end
18
+
19
+ class Hash
20
+ unless RUBY_VERSION >= '2.5'
21
+ def slice!(*keys)
22
+ delete_if { |k| !keys.include?(k) }
23
+ end
24
+
25
+ def slice(*keys)
26
+ dup.slice!(*keys)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'string_format'
4
+ require_relative 'output'
5
+
6
+ module Spellr
7
+ class BaseReporter
8
+ include Spellr::StringFormat
9
+
10
+ def parallel?
11
+ true
12
+ end
13
+
14
+ def initialize(output = nil)
15
+ @output = output
16
+ end
17
+
18
+ def finish
19
+ nil
20
+ end
21
+
22
+ def call(token)
23
+ puts "#{aqua token.location} #{token.line.highlight(token.char_range).strip}"
24
+ end
25
+
26
+ def increment(counter)
27
+ output.increment(counter)
28
+ end
29
+
30
+ def puts(str)
31
+ output.puts(str)
32
+ end
33
+
34
+ def print(str)
35
+ output.print(str)
36
+ end
37
+
38
+ def warn(str)
39
+ output.warn(str)
40
+ end
41
+
42
+ def exit_code
43
+ output.exit_code
44
+ end
45
+
46
+ def output
47
+ @output ||= Spellr::Output.new
48
+ end
49
+
50
+ def counts
51
+ output.counts
52
+ end
53
+ end
54
+ end
data/lib/spellr/check.rb CHANGED
@@ -5,6 +5,9 @@ require_relative 'tokenizer'
5
5
  require_relative 'token'
6
6
  require_relative 'column_location'
7
7
  require_relative 'line_location'
8
+ require_relative 'output_stubbed'
9
+
10
+ require 'parallel'
8
11
 
9
12
  module Spellr
10
13
  class InvalidByteSequence
@@ -15,45 +18,76 @@ module Spellr
15
18
  end
16
19
 
17
20
  class Check
18
- attr_reader :exit_code
19
21
  attr_reader :files, :reporter
20
22
 
23
+ def exit_code
24
+ reporter.exit_code
25
+ end
26
+
21
27
  def initialize(files: [], reporter: Spellr.config.reporter)
22
28
  @files = files
23
- @reporter = reporter
24
- @exit_code = 0
29
+
30
+ @main_reporter = @reporter = reporter
25
31
  end
26
32
 
27
33
  def check
28
- checked = 0
34
+ return check_parallel if reporter.parallel?
35
+
29
36
  files.each do |file|
30
- check_file(file)
31
- checked += 1
37
+ check_and_count_file(file)
38
+ end
39
+
40
+ reporter.finish
41
+ end
42
+
43
+ def check_parallel # rubocop:disable Metrics/MethodLength
44
+ acc_reporter = @reporter
45
+ Parallel.each(files, finish: ->(_, _, result) { acc_reporter.output << result }) do |file|
46
+ @reporter = acc_reporter.class.new(Spellr::OutputStubbed.new)
47
+ check_and_count_file(file)
48
+ reporter.output
32
49
  end
50
+ @reporter = acc_reporter
33
51
 
34
- reporter.finish(checked) if reporter.respond_to?(:finish)
52
+ reporter.finish
35
53
  end
36
54
 
37
55
  private
38
56
 
39
- def check_file(file, start_at: nil, wordlists: Spellr.config.wordlists_for(file)) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
40
- restart_token = catch(:check_file_from) do
41
- Spellr::Tokenizer.new(file, start_at: start_at).each_token do |token|
42
- next if wordlists.any? { |d| d.include?(token) }
57
+ def check_and_count_file(file)
58
+ check_file(file)
59
+ reporter.output.increment(:checked)
60
+ rescue InvalidByteSequence
61
+ # sometimes files are binary
62
+ reporter.output.warn "Skipped unreadable file: #{file}"
63
+ end
43
64
 
44
- start_at = token.location
65
+ def check_tokens_in_file(file, start_at, wordlist_proc)
66
+ Spellr::Tokenizer.new(file, start_at: start_at)
67
+ .each_token(skip_term_proc: wordlist_proc) do |token|
45
68
  reporter.call(token)
46
- @exit_code = 1
69
+ reporter.output.exit_code = 1
47
70
  end
71
+ end
72
+
73
+ def wordlist_proc_for(file)
74
+ wordlists = Spellr.config.wordlists_for(file)
75
+
76
+ ->(term) { wordlists.any? { |w| w.include?(term) } }
77
+ end
78
+
79
+ def check_file_from_restart(file, restart_token, wordlist_proc)
80
+ # new wordlist cache when adding a word
81
+ wordlist_proc = wordlist_proc_for(file) unless restart_token.replacement
82
+ check_file(file, start_at: restart_token.location, wordlist_proc: wordlist_proc)
83
+ end
84
+
85
+ def check_file(file, start_at: nil, wordlist_proc: wordlist_proc_for(file))
86
+ restart_token = catch(:check_file_from) do
87
+ check_tokens_in_file(file, start_at, wordlist_proc)
48
88
  nil
49
89
  end
50
- if restart_token
51
- wordlist_arg = restart_token.replacement ? { wordlists: wordlists } : {} # new wordlist cache when adding a word
52
- check_file(file, start_at: restart_token.location, **wordlist_arg)
53
- end
54
- rescue InvalidByteSequence
55
- # sometimes files are binary
56
- warn "Skipped unreadable file: #{file}" unless Spellr.config.quiet?
90
+ check_file_from_restart(file, restart_token, wordlist_proc) if restart_token
57
91
  end
58
92
  end
59
93
  end
data/lib/spellr/cli.rb CHANGED
@@ -18,10 +18,8 @@ module Spellr
18
18
 
19
19
  def check
20
20
  require_relative 'check'
21
- unless Spellr.config.valid?
22
- Spellr.config.print_errors
23
- exit 1
24
- end
21
+
22
+ validate_config
25
23
 
26
24
  checker = Spellr::Check.new(files: files)
27
25
  checker.check
@@ -29,6 +27,13 @@ module Spellr
29
27
  exit checker.exit_code
30
28
  end
31
29
 
30
+ def validate_config
31
+ return true if Spellr.config.valid?
32
+
33
+ Spellr.config.print_errors
34
+ exit 1
35
+ end
36
+
32
37
  def files
33
38
  require_relative 'file_list'
34
39
  Spellr::FileList.new(*argv)
@@ -41,7 +46,8 @@ module Spellr
41
46
 
42
47
  def quiet_option(_)
43
48
  Spellr.config.quiet = true
44
- Spellr.config.reporter = ->(_) {}
49
+ require_relative 'quiet_reporter'
50
+ Spellr.config.reporter = Spellr::QuietReporter.new
45
51
  end
46
52
 
47
53
  def interactive_option(_)
@@ -89,7 +95,8 @@ module Spellr
89
95
  opts.separator('')
90
96
  opts.on('-w', '--wordlist', 'Outputs errors in wordlist format', &method(:wordlist_option))
91
97
  opts.on('-q', '--quiet', 'Silences output', &method(:quiet_option))
92
- opts.on('-i', '--interactive', 'Runs the spell check interactively', &method(:interactive_option))
98
+ opts.on('-i', '--interactive', 'Runs the spell check interactively',
99
+ &method(:interactive_option))
93
100
  opts.separator('')
94
101
  opts.on('-d', '--dry-run', 'List files to be checked', &method(:dry_run_option))
95
102
  opts.separator('')
@@ -4,9 +4,9 @@ require_relative 'line_location'
4
4
 
5
5
  module Spellr
6
6
  class ColumnLocation
7
- attr_reader :line_location
8
7
  attr_reader :char_offset
9
8
  attr_reader :byte_offset
9
+ attr_accessor :line_location
10
10
 
11
11
  def initialize(char_offset: 0, byte_offset: 0, line_location: LineLocation.new)
12
12
  @line_location = line_location
data/lib/spellr/config.rb CHANGED
@@ -7,7 +7,7 @@ require_relative 'reporter'
7
7
  require 'pathname'
8
8
 
9
9
  module Spellr
10
- class Config # rubocop:disable Metrics/ClassLength
10
+ class Config
11
11
  attr_writer :reporter
12
12
  attr_reader :config_file
13
13
  attr_accessor :quiet
@@ -48,47 +48,17 @@ module Spellr
48
48
  end
49
49
 
50
50
  def includes
51
- return @includes if defined?(@includes)
52
-
53
- if @config[:only]
54
- warn <<~WARNING
55
- \e[33mSpellr: `only:` yaml key with a list of fnmatch rules is deprecated.
56
- Please use `includes:` instead, which uses gitignore-inspired rules.
57
- see github.com/robotdana/fast_ignore#using-an-includes-list for details\e[0m
58
- WARNING
59
- end
60
-
61
- @includes = (@config[:includes] || []) + (@config[:only] || [])
51
+ @includes ||= @config[:includes] || []
62
52
  end
63
53
 
64
54
  def excludes
65
- return @excludes if defined?(@excludes)
66
-
67
- if @config[:ignore]
68
- warn <<~WARNING
69
- \e[33mSpellr: `ignore:` yaml key is deprecated.
70
- Please use `excludes:` instead.\e[0m
71
- WARNING
72
- end
73
-
74
- @excludes = (@config[:excludes] || []) + (@config[:ignore] || [])
55
+ @excludes ||= @config[:excludes] || []
75
56
  end
76
57
 
77
58
  def color
78
59
  @config[:color]
79
60
  end
80
61
 
81
- def clear_cache # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
82
- remove_instance_variable(:@wordlists) if defined?(@wordlists)
83
- remove_instance_variable(:@languages) if defined?(@languages)
84
- remove_instance_variable(:@errors) if defined?(@errors)
85
- remove_instance_variable(:@word_minimum_length) if defined?(@word_minimum_length)
86
- remove_instance_variable(:@key_heuristic_weight) if defined?(@key_heuristic_weight)
87
- remove_instance_variable(:@key_minimum_length) if defined?(@key_minimum_length)
88
- remove_instance_variable(:@excludes) if defined?(@excludes)
89
- remove_instance_variable(:@includes) if defined?(@includes)
90
- end
91
-
92
62
  def languages
93
63
  @languages ||= @config[:languages].map do |key, args|
94
64
  Spellr::Language.new(key, args)
@@ -103,10 +73,6 @@ module Spellr
103
73
  languages.select { |l| l.matches?(file) }
104
74
  end
105
75
 
106
- def wordlists
107
- @wordlists ||= languages.flat_map(&:wordlists)
108
- end
109
-
110
76
  def wordlists_for(file)
111
77
  languages_for(file).flat_map(&:wordlists)
112
78
  end
@@ -123,21 +89,16 @@ module Spellr
123
89
  private
124
90
 
125
91
  def only_has_one_key_per_language
126
- conflicting_languages = languages
127
- .group_by(&:key)
128
- .values.select { |g| g.length > 1 }
129
-
130
- return if conflicting_languages.empty?
92
+ conflicting_languages = languages.group_by(&:key).values.select { |g| g.length > 1 }
131
93
 
132
94
  conflicting_languages.each do |conflicts|
133
- errors << "Error: #{conflicts.map(&:name).join(' & ')} share the same language key (#{conflicts.first.key}). "\
134
- 'Please define one to be different with `key:`'
95
+ errors << "Error: #{conflicts.map(&:name).join(' & ')} share the same language key "\
96
+ "(#{conflicts.first.key}). Please define one to be different with `key:`"
135
97
  end
136
98
  end
137
99
 
138
100
  def keys_are_single_characters
139
101
  bad_languages = languages.select { |l| l.key.length > 1 }
140
- return if bad_languages.empty?
141
102
 
142
103
  bad_languages.each do |language|
143
104
  errors << "Error: #{language.name} defines a key that is too long (#{language.key}). "\
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'yaml'
4
+
3
5
  module Spellr
4
6
  class ConfigLoader
5
7
  attr_reader :config_file
@@ -9,6 +11,7 @@ module Spellr
9
11
  end
10
12
 
11
13
  def config_file=(value)
14
+ # TODO: nicer error message
12
15
  ::File.read(value) # raise Errno::ENOENT if the file doesn't exist
13
16
  @config_file = value
14
17
  @config = nil
@@ -29,10 +32,12 @@ module Spellr
29
32
  end
30
33
 
31
34
  def load_yaml(path)
32
- require 'yaml'
33
-
34
35
  return {} unless ::File.exist?(path)
35
36
 
37
+ symbolize_yaml_safe_load(path)
38
+ end
39
+
40
+ def symbolize_yaml_safe_load(path)
36
41
  if RUBY_VERSION >= '2.5'
37
42
  YAML.safe_load(::File.read(path), symbolize_names: true)
38
43
  else
@@ -40,12 +45,11 @@ module Spellr
40
45
  end
41
46
  end
42
47
 
43
- def symbolize_names!(obj)
48
+ def symbolize_names!(obj) # rubocop:disable Metrics/MethodLength
44
49
  case obj
45
50
  when Hash
46
51
  obj.keys.each do |key|
47
- value = obj.delete(key)
48
- obj[key.to_sym] = symbolize_names!(value)
52
+ obj[key.to_sym] = symbolize_names!(obj.delete(key))
49
53
  end
50
54
  when Array
51
55
  obj.map! { |ea| symbolize_names!(ea) }
@@ -53,7 +57,7 @@ module Spellr
53
57
  obj
54
58
  end
55
59
 
56
- def merge_config(default, project)
60
+ def merge_config(default, project) # rubocop:disable Metrics/MethodLength
57
61
  if project.is_a?(Array) && default.is_a?(Array)
58
62
  default | project
59
63
  elsif project.is_a?(Hash) && default.is_a?(Hash)
data/lib/spellr/file.rb CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  require 'pathname'
4
4
 
5
- # TODO: maybe just extend pathname if you have to
6
-
7
5
  module Spellr
8
6
  class File < Pathname
9
7
  def self.wrap(file)
@@ -22,5 +20,20 @@ module Spellr
22
20
  def first_line
23
21
  @first_line ||= each_line.first
24
22
  end
23
+
24
+ def relative_path
25
+ @relative_path ||= relative_path_from(Spellr.config.pwd)
26
+ end
27
+
28
+ def insert(string, range)
29
+ read_write do |body|
30
+ body[range] = string
31
+ body
32
+ end
33
+ end
34
+
35
+ def read_write
36
+ write(yield read)
37
+ end
25
38
  end
26
39
  end
@@ -12,34 +12,38 @@ module Spellr
12
12
  @patterns = patterns
13
13
  end
14
14
 
15
+ def each
16
+ fast_ignore.each do |file|
17
+ file = Spellr::File.new(file)
18
+
19
+ yield(file)
20
+ end
21
+ end
22
+
23
+ def to_a
24
+ enum_for(:each).to_a
25
+ end
26
+
27
+ private
28
+
15
29
  # anchored patterns are significantly faster on large codebases
16
30
  def cli_patterns
17
31
  @patterns.map do |pattern|
18
- if pattern.match?(%r{^([/~*]|\.{1,2}/)})
19
- pattern
20
- else
21
- "/#{pattern}"
22
- end
32
+ pattern.sub(%r{^(?!(?:[/~*]|\.{1,2}/))}, '/')
23
33
  end
24
34
  end
25
35
 
26
- def each
36
+ def gitignore_path
27
37
  gitignore = ::File.join(Dir.pwd, '.gitignore')
28
- gitignore = nil unless ::File.exist?(gitignore)
38
+ gitignore if ::File.exist?(gitignore)
39
+ end
29
40
 
41
+ def fast_ignore
30
42
  FastIgnore.new(
31
43
  ignore_rules: Spellr.config.excludes,
32
44
  include_rules: Spellr.config.includes + cli_patterns,
33
- gitignore: gitignore
34
- ).each do |file|
35
- file = Spellr::File.new(file)
36
-
37
- yield(file)
38
- end
39
- end
40
-
41
- def to_a
42
- enum_for(:each).to_a
45
+ gitignore: gitignore_path
46
+ )
43
47
  end
44
48
  end
45
49
  end