gramrb 1.0.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 +7 -0
- data/bin/gramrb +25 -0
- data/lib/gramrb/cli.rb +66 -0
- data/lib/gramrb/config.rb +17 -0
- data/lib/gramrb/content_extractor.rb +28 -0
- data/lib/gramrb/file_scanner.rb +21 -0
- data/lib/gramrb/formatter.rb +13 -0
- data/lib/gramrb/spell_checker.rb +35 -0
- data/lib/gramrb/string_cleaner.rb +34 -0
- data/lib/gramrb/version.rb +5 -0
- data/lib/gramrb.rb +10 -0
- metadata +69 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 658d4ce56f5e37c4c41f8cbda02d6aa7b203c75de901704179de3c32a31ca6af
|
|
4
|
+
data.tar.gz: ae6418357aab615ae1fbb5d5b0a4dc9873f4f678fc87f3e844c8e9a2684a65a1
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 6846c62e554f806558933e96c1d146fda16ad4650634c28f575027eec07a4a130098723a6c6508a575383023e7e72f6a7ec5a2d635cff7ac02c01be9366701ce
|
|
7
|
+
data.tar.gz: 70c11c0844b85a40b8957a65339c1d8fcf7518422fe60be0051a59c12c5fafb80db51ee866dd0f36d411fd5815ebef22e41792b59c0f189f200858431a86a79f
|
data/bin/gramrb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require_relative '../lib/gramrb'
|
|
4
|
+
|
|
5
|
+
case ARGV[0]
|
|
6
|
+
when '--version', '-v'
|
|
7
|
+
puts Gramrb::Version::VERSION
|
|
8
|
+
when '--help', '-h'
|
|
9
|
+
puts <<~HELP
|
|
10
|
+
Usage: gramrb [path]
|
|
11
|
+
|
|
12
|
+
Scans .rb files for spelling errors in variable names, method names, comments, and strings.
|
|
13
|
+
|
|
14
|
+
Options:
|
|
15
|
+
-v, --version Print version
|
|
16
|
+
-h, --help Print this help
|
|
17
|
+
HELP
|
|
18
|
+
else
|
|
19
|
+
begin
|
|
20
|
+
Gramrb::CLI.new(ARGV[0]).run
|
|
21
|
+
rescue Errno::ENOENT, ArgumentError => e
|
|
22
|
+
puts "Error: #{e.message}"
|
|
23
|
+
exit 1
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/gramrb/cli.rb
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
module Gramrb
|
|
2
|
+
class CLI
|
|
3
|
+
include Formatter
|
|
4
|
+
|
|
5
|
+
def initialize(path) = @path = path || '.'
|
|
6
|
+
|
|
7
|
+
def run
|
|
8
|
+
puts
|
|
9
|
+
results = scan_files
|
|
10
|
+
puts "\n"
|
|
11
|
+
print_results(results)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def scan_files
|
|
17
|
+
files.each_with_object({}) do |file, results|
|
|
18
|
+
results.merge!(FileScanner.new(file).scan)
|
|
19
|
+
print results.key?(file) ? red('F') : green('.')
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def print_results(results)
|
|
24
|
+
puts "\nPossible spelling errors found:" if results.any?
|
|
25
|
+
results.each do |path, errors|
|
|
26
|
+
puts "#{cyan(path)} #{red("(#{pluralise(file_error_count(errors), 'error')})")}"
|
|
27
|
+
|
|
28
|
+
print_filename_errors(errors[:filename_errors])
|
|
29
|
+
print_content_errors(path, errors[:content_errors])
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
print_summary(total_errors(results))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def print_filename_errors(errors)
|
|
36
|
+
errors.each do |error|
|
|
37
|
+
puts " - #{cyan('Filename: ')}#{highlight(error)} #{green(%{(Did you mean: "#{error[:suggestion]}"?)})}"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def print_content_errors(path, errors)
|
|
42
|
+
errors.each do |error|
|
|
43
|
+
puts " - #{cyan(path)}:#{error[:line]} #{highlight(error)} #{green(%{(Did you mean: "#{error[:suggestion]}"?)})}"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def print_summary(total)
|
|
48
|
+
summary = "found #{pluralise(total, 'error')}"
|
|
49
|
+
puts "\n#{pluralise(files.count, 'file')} scanned, #{total.zero? ? green(summary) : red(summary)}"
|
|
50
|
+
puts yellow("\nTo ignore a word, add it to your .gramrb.yml allowlist.")
|
|
51
|
+
puts yellow('Think it should be in the master allowlist? Open an issue at https://github.com/Pandaman74/Gramrb/issues')
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def highlight(error) = error[:item].gsub(error[:error], red(error[:error]))
|
|
55
|
+
|
|
56
|
+
def files
|
|
57
|
+
@files ||= File.file?(@path) ? [@path] : Dir.glob("#{@path}/**/*.rb").reject { |file| config.excluded.any? { |excluded| file.include?(excluded) } }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def config = @config ||= Config.new
|
|
61
|
+
|
|
62
|
+
def file_error_count(errors) = (errors[:filename_errors] + errors[:content_errors]).count
|
|
63
|
+
|
|
64
|
+
def total_errors(results) = results.values.sum { |errors| file_error_count(errors) }
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
module Gramrb
|
|
3
|
+
class Config
|
|
4
|
+
def allowlisted = (bundled['allowlist'] + (project['allowlist'] || [])).uniq
|
|
5
|
+
|
|
6
|
+
def excluded = project['excluded'] || bundled['excluded']
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def bundled = YAML.load_file(File.join(__dir__, '../../config/.gramrb.yml'))
|
|
11
|
+
|
|
12
|
+
def project
|
|
13
|
+
path = File.join(Dir.pwd, '.gramrb.yml')
|
|
14
|
+
File.exist?(path) ? YAML.load_file(path) : {}
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Gramrb
|
|
2
|
+
class ContentExtractor
|
|
3
|
+
def initialize(content) = @lines = content.lines
|
|
4
|
+
|
|
5
|
+
def extract_all = @lines.flat_map.with_index(1) { |line, line_number| extract_from_line(line, line_number) }
|
|
6
|
+
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def extract_from_line(line, line_number)
|
|
10
|
+
@line = line
|
|
11
|
+
@line_number = line_number
|
|
12
|
+
extract_strings + extract_comments + extract_method_names + extract_variables + extract_symbols
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def extract_strings = @line.scan(/['"]([^'"]*?)['"]/).map { |match| { text: match[0], line_number: @line_number } }
|
|
16
|
+
|
|
17
|
+
def extract_comments = @line.scan(/^\s*#\s*(.*)/).map { |match| { text: match[0], line_number: @line_number } }
|
|
18
|
+
|
|
19
|
+
def extract_method_names = @line.scan(/def\s+([a-zA-Z_]\w*)/).map { |match| { text: match[0], line_number: @line_number } }
|
|
20
|
+
|
|
21
|
+
def extract_variables = @line.scan(/(@?[a-zA-Z_]\w*)\s*=/).map { |match| { text: match[0], line_number: @line_number } }
|
|
22
|
+
|
|
23
|
+
def extract_symbols
|
|
24
|
+
@line.scan(/(:[a-zA-Z_]\w*)/).map { |match| { text: match[0], line_number: @line_number } } +
|
|
25
|
+
@line.scan(/([a-zA-Z_]\w*:)/).map { |match| { text: match[0], line_number: @line_number } }
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Gramrb
|
|
2
|
+
class FileScanner
|
|
3
|
+
def initialize(path)
|
|
4
|
+
raise ArgumentError, ".#{File.extname(path)} files are not supported" if File.extname(path) != '.rb'
|
|
5
|
+
|
|
6
|
+
@path = path
|
|
7
|
+
@filename = File.basename(path, '.rb')
|
|
8
|
+
@content = ContentExtractor.new(File.read(path)).extract_all
|
|
9
|
+
@spell_checker = SpellChecker.new(Config.new.allowlisted)
|
|
10
|
+
@string_cleaner = StringCleaner.new
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def scan = (filename_errors + content_errors).any? ? { @path => { filename_errors:, content_errors: } } : {}
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def filename_errors = @spell_checker.check_words(@string_cleaner.clean_filename(@filename))
|
|
18
|
+
|
|
19
|
+
def content_errors = @spell_checker.check_words(@string_cleaner.clean_content(@content))
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Gramrb
|
|
2
|
+
module Formatter
|
|
3
|
+
def pluralise(count, word) = "#{count} #{count == 1 ? word : "#{word}s"}"
|
|
4
|
+
|
|
5
|
+
def red(str) = "\e[31m#{str}\e[0m"
|
|
6
|
+
|
|
7
|
+
def green(str) = "\e[32m#{str}\e[0m"
|
|
8
|
+
|
|
9
|
+
def yellow(str) = "\e[33m#{str}\e[0m"
|
|
10
|
+
|
|
11
|
+
def cyan(str) = "\e[36m#{str}\e[0m"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require 'tmpdir'
|
|
2
|
+
|
|
3
|
+
module Gramrb
|
|
4
|
+
class SpellChecker
|
|
5
|
+
def initialize(allowlisted)
|
|
6
|
+
pws_path = File.join(Dir.tmpdir, 'gramrb.pws')
|
|
7
|
+
File.write(pws_path, "personal_ws-1.1 en 0\n#{allowlisted.join("\n")}")
|
|
8
|
+
@string_cleaner = StringCleaner.new
|
|
9
|
+
@speller_gb = FFI::Aspell::Speller.new('en_GB', personal: pws_path)
|
|
10
|
+
@speller_us = FFI::Aspell::Speller.new('en_US', personal: pws_path)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def check_words(content)
|
|
14
|
+
content.each_with_object([]) do |item, results|
|
|
15
|
+
bad_words(item[:words]).each do |error|
|
|
16
|
+
suggestion = @speller_us.suggestions(error).first || @speller_gb.suggestions(error).first
|
|
17
|
+
results << { item: item[:item], line: item[:line], error:, suggestion: }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def bad_words(words)
|
|
25
|
+
words.each_with_object([]) do |word, errors|
|
|
26
|
+
next if correct?(word)
|
|
27
|
+
|
|
28
|
+
parts = @string_cleaner.split_camel(word)
|
|
29
|
+
parts.length > 1 ? errors.concat(parts.reject { |part| correct?(part) }) : errors << word
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def correct?(word) = @speller_us.correct?(word) || @speller_gb.correct?(word)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Gramrb
|
|
2
|
+
class StringCleaner
|
|
3
|
+
def clean_filename(name) = [clean_item(name)]
|
|
4
|
+
|
|
5
|
+
def clean_content(content)
|
|
6
|
+
content.filter_map do |item|
|
|
7
|
+
cleaned = clean_item(item[:text])
|
|
8
|
+
cleaned[:line] = item[:line_number]
|
|
9
|
+
cleaned if cleaned[:words].any?
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def split_camel(item) = item.gsub(/([a-z])([A-Z])/, '\1 \2').split.select { |word| word?(word) }
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def clean_item(item)
|
|
18
|
+
return { item:, words: [] } if invalid?(item)
|
|
19
|
+
|
|
20
|
+
words = item.gsub(%r{https?://\S+}, ' ')
|
|
21
|
+
.gsub(/([a-zA-Z])(\d)/, '\1 \2')
|
|
22
|
+
.gsub(/(\d)([a-zA-Z])/, '\1 \2')
|
|
23
|
+
.gsub(/[^a-zA-Z\s]/, ' ')
|
|
24
|
+
.split
|
|
25
|
+
.select { |word| word?(word) }
|
|
26
|
+
|
|
27
|
+
{ item:, words: }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def invalid?(item) = item.nil? || item.empty?
|
|
31
|
+
|
|
32
|
+
def word?(word) = word.length > 1
|
|
33
|
+
end
|
|
34
|
+
end
|
data/lib/gramrb.rb
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
require 'ffi/aspell'
|
|
2
|
+
|
|
3
|
+
require_relative 'gramrb/formatter'
|
|
4
|
+
require_relative 'gramrb/cli'
|
|
5
|
+
require_relative 'gramrb/config'
|
|
6
|
+
require_relative 'gramrb/content_extractor'
|
|
7
|
+
require_relative 'gramrb/file_scanner'
|
|
8
|
+
require_relative 'gramrb/spell_checker'
|
|
9
|
+
require_relative 'gramrb/string_cleaner'
|
|
10
|
+
require_relative 'gramrb/version'
|
metadata
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: gramrb
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Pandaman74
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: ffi-aspell
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.1'
|
|
26
|
+
description: |
|
|
27
|
+
Gramrb is a spellchecking tool for Ruby code files, mostly for use within Ruby on Rails.
|
|
28
|
+
It uses Aspell to spellcheck aspects such as comments, method names, and variable names.
|
|
29
|
+
email: pandamanprojects@gmail.com
|
|
30
|
+
executables:
|
|
31
|
+
- gramrb
|
|
32
|
+
extensions: []
|
|
33
|
+
extra_rdoc_files: []
|
|
34
|
+
files:
|
|
35
|
+
- bin/gramrb
|
|
36
|
+
- lib/gramrb.rb
|
|
37
|
+
- lib/gramrb/cli.rb
|
|
38
|
+
- lib/gramrb/config.rb
|
|
39
|
+
- lib/gramrb/content_extractor.rb
|
|
40
|
+
- lib/gramrb/file_scanner.rb
|
|
41
|
+
- lib/gramrb/formatter.rb
|
|
42
|
+
- lib/gramrb/spell_checker.rb
|
|
43
|
+
- lib/gramrb/string_cleaner.rb
|
|
44
|
+
- lib/gramrb/version.rb
|
|
45
|
+
homepage: https://github.com/Pandaman74/Gramrb
|
|
46
|
+
licenses:
|
|
47
|
+
- MIT
|
|
48
|
+
metadata:
|
|
49
|
+
rubygems_mfa_required: 'true'
|
|
50
|
+
homepage_uri: https://github.com/Pandaman74/Gramrb
|
|
51
|
+
source_code_uri: https://github.com/Pandaman74/Gramrb
|
|
52
|
+
rdoc_options: []
|
|
53
|
+
require_paths:
|
|
54
|
+
- lib
|
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - ">="
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '4.0'
|
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
61
|
+
requirements:
|
|
62
|
+
- - ">="
|
|
63
|
+
- !ruby/object:Gem::Version
|
|
64
|
+
version: '0'
|
|
65
|
+
requirements: []
|
|
66
|
+
rubygems_version: 4.0.3
|
|
67
|
+
specification_version: 4
|
|
68
|
+
summary: Spellchecker for .rb code files
|
|
69
|
+
test_files: []
|