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 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
@@ -0,0 +1,5 @@
1
+ module Gramrb
2
+ module Version
3
+ VERSION = '1.0.0'.freeze
4
+ end
5
+ 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: []