spellr 0.5.0 → 0.5.1

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 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