spellr 0.4.1 → 0.5.0

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: e259544899eb28d6a4b19e521eed16b12bb316488241d3a79af01c22873515f1
4
- data.tar.gz: f519dcb4f8a434e1cc619f389c0719263d894ca44ac3996b07acef89afcef1c4
3
+ metadata.gz: b12a7a52fc68f4960a9156f4d500366225de366b3ca20213231a22176c898030
4
+ data.tar.gz: 816aafdee27ec4c997e96b3726f37f1e08dc22aaee9f29680aee9f86b8481693
5
5
  SHA512:
6
- metadata.gz: eef1d7df649bf7f6b1a5b2c3e7cac423627b4aea17221e8ca5464c380d5de9ee7fba45183afc5373aeaa974272a7cd7127809935cdd3addae94d30c0d9f858d4
7
- data.tar.gz: c314a2230af151f7098cc344d78b90f9b3c9696870b1056cc50ac3b5547cdbd367e62ebb98e2bdbd101f130881b80b4f050cd71ffc9db3c62fd16e074ac5c1e3
6
+ metadata.gz: 52e520722759e56c001ee0271408a7bb87e47cfb3ed1cba15ae1c208356a26cae1be01f05e72c316a6141e0e7e71aebe3878862de975cf12eedd77e64c2c1c3f
7
+ data.tar.gz: 3f11b304e48cc73823dd66b23daec1622ab1ee0e94545929a15d0baf80a4fc616a3dfa8f2ec9dda28e9792701d277fb0dc8e3cbbf673531dc8aca3fe9947ffd0
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ # v0.5.0
2
+ - Removed the fetch command it was just unnecessarily slow and awkward. instead use `locale: [US,AU]`
3
+ - Added usage documentation
4
+ - Fixed an issue where file-specific wordlists couldn't be added to
5
+
1
6
  # v0.4.1
2
7
  - fix the private method 'touch' issue when generating wordlists
3
8
  - fix the js/javascript defaults being named differently (now consistently is named javascript)
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- spellr (0.4.1)
4
+ spellr (0.5.0)
5
5
  fast_ignore (~> 0.4.0)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -4,29 +4,209 @@
4
4
 
5
5
  Spell check your source code for fun and occasionally finding bugs
6
6
 
7
- This is inspired by https://github.com/myint/scspell but i wanted a ruby gem
7
+ This is inspired by https://github.com/myint/scspell, and uses wordlists from [SCOWL](http://wordlist.aspell.net) - see the license [here](https://github.com/robotdana/spellr/blob/master/wordlists/english.LICENSE.txt).
8
+
9
+ ## What makes a spell checker a source code spell checker?
10
+
11
+ 1. It tokenizes CamelCase and snake_case and kebab-case and checks these as independent words including CAMELCase with acronyms.
12
+ 2. It skips urls
13
+ 3. It skips things that heuristically look like base64 or hex strings rather than words. This uses a bayesian classifier and is not magic. Find the balance of false-positive to false-negative that works for you with the `key_heuristic_weight` [configuration](#configuration) option.
14
+ 4. It comes with some wordlists for built in commands in some common programming languages, and recognizes hashbangs.
15
+ 5. Configure whether you want US, AU, CA, or GB english (or all of them).
16
+ 6. It checks directories recursively, obeying .gitignore
17
+ 7. It's easy to add terms to wordlists
18
+ 8. It's easy to integrate with CI pipelines
19
+ 7. It's very configurable
20
+
21
+ ## A brief aside on "correct" spelling.
22
+
23
+ There's no correct way to spell anything. You can't trust dictionaries, they only react to the way everyone else uses words. Any agreement about certain spellings is a collective hallucination, and is a terrible proxy for attention or intelligence or education or value. Those who get to declare what "correct" spelling is, or even what counts as a real word, tend to be those groups that have more social power and it's (sometimes unconsciously) used as a way to maintain that power.
24
+
25
+ However, in a programming context spelling things _consistently_ is useful, where method definitions must match method calls, and comments about these are clearer when also matching. It also makes grepping easier, not that you'd find the word 'grepping' in most dictionaries.
8
26
 
9
27
  ## Installation
10
28
 
11
- Add this line to your application's Gemfile:
29
+ ### With Bundler
30
+
31
+ Add this line to your application's `Gemfile`:
12
32
 
13
33
  ```ruby
14
34
  gem 'spellr', require: false
15
35
  ```
16
36
 
17
- And then execute:
37
+ Then execute:
38
+
39
+ ```bash
40
+ $ bundle install
41
+ ```
42
+
43
+ ### With Rubygems
18
44
 
19
- $ bundle
45
+ ```bash
46
+ $ gem install spellr
47
+ ```
20
48
 
21
- Or install it yourself as:
49
+ ### With Docker
22
50
 
23
- $ gem install spellr
51
+ execute this command instead of `spellr`. This is otherwise identical to using the gem version
52
+
53
+ ```bash
54
+ $ docker run -it -v $PWD:/app robotdana/spellr
55
+ ```
24
56
 
25
57
  ## Usage
26
58
 
27
- To start an interactive spell checking session
59
+ The main way to interact with `spellr` is through the executable.
60
+
61
+ ```bash
62
+ $ spellr # will run the spell checker
63
+ $ spellr --interactive # will run the spell checker, interactively
64
+ $ spellr --wordlist # will output all words that fail the spell checker in spellr wordlist format
65
+ $ spellr --quiet # will suppress all output
66
+ ```
67
+
68
+ To check a single file or subset of files, just add paths or globs:
69
+ ```bash
70
+ $ spellr --interactive path/to/my/file.txt and/another/file.sh
71
+ $ spellr --wordlist '*.rb' '*_test.js'
72
+ ```
73
+
74
+ There are some support commands available:
75
+
76
+ ```bash
77
+ $ spellr --dry-run # list files that will be checked
78
+ $ spellr --version # for the current version
79
+ $ spellr --help # for the list of flags available
80
+ ```
81
+
82
+ ### First run
83
+
84
+ Feel free to just `spellr --interactive` and go, but I prefer this process when first adding spellr to a large project.
85
+
86
+ ```bash
87
+ $ spellr --dry-run
88
+ ```
89
+
90
+ Look at the list of files, are there some that shouldn't be checked (generated files etc)? .gitignored files and some binary file extensions are already skipped by default.
91
+
92
+ Add any additional files to ignore to a `.spellr.yml` file in your project root directory.
93
+ ```yml
94
+ excludes:
95
+ - ignore
96
+ - /generated
97
+ - "!files"
98
+ - in/*
99
+ - .gitignore
100
+ - "*.format"
101
+ ```
102
+
103
+ Then output the existing words that fail the default dictionaries.
104
+ ```bash
105
+ $ spellr --wordlist > .spellr-wordlists/english.txt
106
+ ```
107
+
108
+ Open `.spellr-wordlists/english.txt` and remove those lines that look like typos or mistakes, leaving the file in ascii order.
109
+
110
+ Now it's time to run the interactive spell checker
111
+
112
+ ```bash
113
+ $ spellr --interactive
114
+ ```
115
+
116
+ ### Interactive spell checking
117
+
118
+ To start an interactive spell checking session:
119
+ ```bash
120
+ $ spellr --interactive
121
+ ```
122
+
123
+ You'll be shown each word that's not found in a dictionary, it's location (path:line:column), along with a prompt.
124
+ ```
125
+ file.rb:1:0 notaword
126
+ [r,R,s,S,a,e?]
127
+ ```
128
+
129
+ Type `?` for this list of what each letter command does
130
+ ```
131
+ [r] Replace notaword
132
+ [R] Replace all future instances of notaword
133
+ [s] Skip notaword
134
+ [S] Skip all future instances of notaword
135
+ [a] Add notaword to a word list
136
+ [e] Edit the whole line
137
+ [?] Show this help
138
+ ```
139
+
140
+ If you type `r`, `R` or `e` you'll be shown a prompt with the original word and it prefilled ready for correcting:
141
+ ```
142
+ file.txt:1:0 notaword
143
+ >> notaword
144
+ => not_a_word
145
+ ```
146
+ To submit your choice and continue with the spell checking click enter. Your replacement word will be immediately spellchecked. To instead go back press Ctrl-C once (pressing it twice will exit the spell checking).
147
+
148
+ If you instead type `s` or `S` it will skip this word and continue with the spell checking.
149
+
150
+ ---
151
+
152
+ If you instead type `a` you'll be shown a list of possible wordlists to add to. This list is based on the file path, and is configurable in `.spellr.yml`.
153
+ ```
154
+ Add notaword to wordlist:
155
+ [e] english
156
+ [r] ruby
157
+ ```
158
+ Type `e` to add this word to the english wordlist and continue on through the spell checking. To instead go back to the prompt press Ctrl-C once (pressing it twice will exit the spell checking).
159
+
160
+ ### Disabling the tokenizer
161
+
162
+ If the tokenizer finds a word you don't want to add to the wordlist (perhaps it's an intentional example of a typo, or a non-word string not excluded by the heuristic) then place on the lines before and after
163
+ ```ruby
164
+ # spellr:disable
165
+ "Test typo of the: teh"
166
+ # spellr:enable
167
+ ```
168
+
169
+ ## Configuration
170
+
171
+ Spellr's configuration is a `.spellr.yml` file in your project root. This is combined with the gem defaults defined [here](https://github.com/robotdana/spellr/blob/master/lib/.spellr.yml).
172
+ There are top-level keys and per-language keys.
173
+ ```yml
174
+ word_minimum_length: 3 # any words shorter than this will be ignored
175
+ key_minimum_length: 6 # any strings shorter than this won't be considered non-word strings
176
+ key_heuristic_weight: 5 # higher values mean strings are more likely to be considered words or non-words by the classifier.
177
+ excludes:
178
+ - ignore
179
+ - "!files"
180
+ - in/*
181
+ - .gitignore
182
+ - "*.format"
183
+ includes:
184
+ - limit to
185
+ - "files*"
186
+ - in/*
187
+ - .gitignore-esque
188
+ - "*.format"
28
189
  ```
29
- spellr --interactive
190
+ The includes format is documented [here](https://github.com/robotdana/fast_ignore#using-an-includes-list).
191
+
192
+ Also within this file are language definitions:
193
+ ```yml
194
+ languages:
195
+ english: # this must match exactly the name of the file in .spellr-wordlists/
196
+ locale: # US, AU, CA, or GB
197
+ - US
198
+ - AU
199
+ ruby:
200
+ includes:
201
+ - patterns*
202
+ - "*_here.rb"
203
+ - limit-which-files
204
+ - the/wordlist/**/*
205
+ - /applies_to/
206
+ key: r # this is the letter used to choose this wordlist when using `spellr --interactive`.
207
+ hashbangs:
208
+ - ruby # if the hashbang contains ruby, this file will match,
209
+ # even if it doesn't otherwise match the includes pattern.
30
210
  ```
31
211
 
32
212
  ## Development
data/lib/.spellr.yml CHANGED
@@ -28,8 +28,7 @@ excludes: # this list is parsed with the .gitignore format
28
28
 
29
29
  languages:
30
30
  english:
31
- generate: "fetch english"
32
- # TODO: don't generate the ruby file until you actually need one
31
+ locale: US # options US, CA, AU, GBs (GB with -ise endings), GBz (GB with -ize endings)
33
32
  ruby:
34
33
  includes: # Filtered using gitignore format
35
34
  - '*.rb'
data/lib/spellr/cli.rb CHANGED
@@ -7,8 +7,7 @@ require 'open3'
7
7
  require_relative '../spellr'
8
8
 
9
9
  module Spellr
10
- class CLI # rubocop:disable Metrics/ClassLength
11
- attr_writer :fetch_output_dir
10
+ class CLI
12
11
  attr_reader :argv
13
12
 
14
13
  def initialize(argv)
@@ -67,108 +66,13 @@ module Spellr
67
66
  exit
68
67
  end
69
68
 
70
- def get_wordlist_option(command)
71
- get_wordlist_dir.join(command)
72
- end
73
-
74
- def fetch_output_dir
75
- @fetch_output_dir ||= Pathname.pwd.join('.spellr_wordlists/generated').expand_path
76
- end
77
-
78
- def fetch_words_for_wordlist(wordlist)
79
- wordlist_command(wordlist, *argv)
80
- end
81
-
82
- def wordlist_command(wordlist, *args)
83
- require 'shellwords'
84
- command = fetch_wordlist_dir.join(wordlist).to_s
85
- fetch_output_dir.mkpath
86
-
87
- command_with_args = args.unshift(command).shelljoin
88
-
89
- out, err, status = Open3.capture3(command_with_args)
90
- puts err unless err.empty?
91
- return out if status.exitstatus == 0
92
-
93
- exit
94
- end
95
-
96
- def replace_wordlist(words, wordlist)
97
- require_relative '../../lib/spellr/wordlist'
98
-
99
- Spellr::Wordlist.new(fetch_output_dir.join("#{wordlist}.txt")).clean(StringIO.new(words))
100
- end
101
-
102
- def extract_and_write_license(words, wordlist)
103
- words, license = words.split('---', 2).reverse
104
-
105
- fetch_output_dir.join("#{wordlist}.LICENSE.txt").write(license) if license
106
-
107
- words
108
- end
109
-
110
- def fetch
111
- wordlist = argv.shift
112
- puts "Fetching #{wordlist} wordlist"
113
- words = fetch_words_for_wordlist(wordlist)
114
- puts "Preparing #{wordlist} wordlist"
115
- words = extract_and_write_license(words, wordlist)
116
- puts "cleaning #{wordlist} wordlist"
117
- replace_wordlist(words, wordlist)
118
- end
119
-
120
- def output_option(dir)
121
- self.fetch_output_dir = Pathname.pwd.join(dir).expand_path
122
- end
123
-
124
- def wordlists
125
- fetch_wordlist_dir.children.map { |p| p.basename.to_s }
126
- end
127
-
128
- def fetch_wordlist_dir
129
- @fetch_wordlist_dir ||= Pathname.new(__dir__).parent.parent.join('bin', 'fetch_wordlist').expand_path
130
- end
131
-
132
69
  def parse_command
133
- case argv.first
134
- when 'fetch'
135
- parse_fetch_options
136
- fetch
137
- else
138
- parse_options
139
- check
140
- end
141
- end
142
-
143
- def fetch_options
144
- @fetch_options ||= begin
145
- opts = OptionParser.new
146
- opts.banner = "Usage: spellr fetch [options] WORDLIST [wordlist options]\nAvailable wordlists: #{wordlists}"
147
-
148
- opts.separator('')
149
- opts.on('-o', '--output=OUTPUT', 'Outputs the fetched wordlist to OUTPUT/WORDLIST.txt', &method(:output_option))
150
- opts.on('-h', '--help', 'Shows help for fetch', &method(:fetch_options_help))
151
-
152
- opts
153
- end
154
- end
155
-
156
- def fetch_options_help(*_)
157
- puts fetch_options.help
158
-
159
- wordlist = argv.first
160
- if wordlist
161
- puts
162
- wordlist_command('english', '--help')
163
- end
164
-
165
- exit
70
+ parse_options
71
+ check
166
72
  end
167
73
 
168
74
  def options_help(_)
169
75
  puts options.help
170
- puts
171
- puts fetch_options.help
172
76
 
173
77
  exit
174
78
  end
@@ -177,14 +81,6 @@ module Spellr
177
81
  options.parse!(argv)
178
82
  end
179
83
 
180
- def parse_fetch_options
181
- argv.shift
182
- fetch_options.order!(argv) do |non_arg|
183
- argv.unshift(non_arg)
184
- break
185
- end
186
- end
187
-
188
84
  def options # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
189
85
  @options ||= begin
190
86
  opts = OptionParser.new
@@ -50,7 +50,7 @@ module Spellr
50
50
  end
51
51
 
52
52
  def prompt(token)
53
- print bold('[a,s,S,r,R,e,?]')
53
+ print bold('[r,R,s,S,a,e,?]')
54
54
 
55
55
  handle_response(token)
56
56
  rescue Interrupt
@@ -120,10 +120,10 @@ module Spellr
120
120
  # TODO: handle more than 16 options
121
121
  def handle_add(token) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
122
122
  puts "Add #{red(token)} to wordlist:"
123
- languages = Spellr.config.languages_for(token.location.file)
123
+ languages = Spellr.config.languages_for(token.location.file.path)
124
124
 
125
- languages.each do |wordlist|
126
- puts "[#{wordlist.key}] #{wordlist.name}"
125
+ languages.each do |language|
126
+ puts "[#{language.key}] #{language.name}"
127
127
  end
128
128
  choice = STDIN.getch
129
129
  clear_current_line
@@ -13,7 +13,8 @@ module Spellr
13
13
  only: [],
14
14
  includes: [],
15
15
  description: '',
16
- hashbangs: [])
16
+ hashbangs: [],
17
+ locale: [])
17
18
  unless only.empty?
18
19
  warn <<~WARNING
19
20
  \e[33mSpellr: `only:` language yaml key with a list of fnmatch rules is deprecated.
@@ -21,12 +22,20 @@ module Spellr
21
22
  see github.com/robotdana/fast_ignore#using-an-includes-list for details\e[0m
22
23
  WARNING
23
24
  end
25
+
26
+ if generate
27
+ warn <<~WARNING
28
+ \e[33mSpellr: `generate:` and generation is now deprecated. Choose the language
29
+ using the key `locale:` (any of US,AU,CA,GB,GBz,GBs as a string or array).\e[0m
30
+ WARNING
31
+ end
32
+
24
33
  @name = name
25
34
  @key = key
26
35
  @description = description
27
- @generate = generate
28
36
  @includes = only + includes
29
37
  @hashbangs = hashbangs
38
+ @locales = Array(locale)
30
39
  end
31
40
 
32
41
  def matches?(file)
@@ -43,24 +52,9 @@ module Spellr
43
52
  end
44
53
 
45
54
  def wordlists
46
- generate_wordlist unless generated_project_wordlist.exist?
47
55
  default_wordlists.select(&:exist?)
48
56
  end
49
57
 
50
- def generate_wordlist
51
- return [] unless generate
52
-
53
- require_relative 'cli'
54
- require 'shellwords'
55
- warn "Generating wordlist for #{name}"
56
-
57
- generated_project_wordlist.touch
58
-
59
- Spellr::CLI.new(generate.shellsplit)
60
-
61
- default_wordlists
62
- end
63
-
64
58
  def gem_wordlist
65
59
  @gem_wordlist ||= Spellr::Wordlist.new(
66
60
  Pathname.new(__dir__).parent.parent.join('wordlists', "#{name}.txt")
@@ -74,17 +68,17 @@ module Spellr
74
68
  )
75
69
  end
76
70
 
77
- def generated_project_wordlist
78
- @generated_project_wordlist ||= Spellr::Wordlist.new(
79
- Pathname.pwd.join('.spellr_wordlists', 'generated', "#{name}.txt")
80
- )
71
+ def locale_wordlists
72
+ @locale_wordlists ||= @locales.map do |locale|
73
+ Spellr::Wordlist.new(
74
+ Pathname.new(__dir__).parent.parent.join('wordlists', name.to_s, "#{locale}.txt")
75
+ )
76
+ end
81
77
  end
82
78
 
83
79
  private
84
80
 
85
- attr_reader :generate
86
-
87
- def load_wordlists(name, paths, _generate)
81
+ def load_wordlists(name, paths)
88
82
  wordlists = paths + default_wordlist_paths(name)
89
83
 
90
84
  wordlists.map(&Spellr::Wordlist.method(:new))
@@ -93,8 +87,8 @@ module Spellr
93
87
  def default_wordlists
94
88
  [
95
89
  gem_wordlist,
96
- generated_project_wordlist,
97
- project_wordlist
90
+ project_wordlist,
91
+ *locale_wordlists
98
92
  ]
99
93
  end
100
94
  end