danger-spelling 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/branch-checks.yml +26 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +7 -0
- data/Dangerfile +10 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +177 -0
- data/Guardfile +21 -0
- data/LICENSE.txt +22 -0
- data/README.md +127 -0
- data/Rakefile +25 -0
- data/danger-spelling.gemspec +50 -0
- data/lib/danger_plugin.rb +3 -0
- data/lib/danger_spelling.rb +3 -0
- data/lib/spelling/gem_version.rb +5 -0
- data/lib/spelling/plugin.rb +370 -0
- data/spec/fixtures/github_pr.json +350 -0
- data/spec/fixtures/spell_issues.txt +7 -0
- data/spec/fixtures/test.yml +2 -0
- data/spec/spec_helper.rb +67 -0
- data/spec/spelling_spec.rb +189 -0
- metadata +209 -0
@@ -0,0 +1,370 @@
|
|
1
|
+
require 'English'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module Danger
|
5
|
+
# This is a Danger plugin that wraps the python library pyspelling and some of its usage.
|
6
|
+
# The pyspelling results are posted to the pull request as a comment with the spelling mistake, file path &
|
7
|
+
# line number where the spelling mistake was found.
|
8
|
+
#
|
9
|
+
# It has some dependencies that should be installed prior to running.
|
10
|
+
#
|
11
|
+
# * [pyspelling](https://facelessuser.github.io/pyspelling/)
|
12
|
+
# * [aspell](http://aspell.net)
|
13
|
+
# * **OR**
|
14
|
+
# * [hunspell](http://hunspell.github.io)
|
15
|
+
#
|
16
|
+
# Your repository will also require a .pyspelling.yml file to be present. This .pyspelling.yml can be basic,
|
17
|
+
# but it will require a name and source property. Its advisable to include `expect_match: false` in your test
|
18
|
+
# matrix. This will stop pyspelling from generating an error at runtime.
|
19
|
+
#
|
20
|
+
# There are several ways to use this danger plugin
|
21
|
+
#
|
22
|
+
# @example execute pyspelling matrix with the name 'test_matrix' on all files modified or
|
23
|
+
# added in the given pull request.
|
24
|
+
# spelling.name = "test_matrix"
|
25
|
+
# spelling.check_spelling
|
26
|
+
#
|
27
|
+
# @see HammyAssassin/danger-spelling
|
28
|
+
# @tags spelling, danger, pyspelling, hunspell, aspell
|
29
|
+
#
|
30
|
+
#
|
31
|
+
# @example execute pyspelling matrix with the name 'test_matrix' on all files modified or added in the given pull
|
32
|
+
# request, excluding some specific file names
|
33
|
+
# spelling.ignored_files = ["Gemfile"]
|
34
|
+
# spelling.name = "test_matrix"
|
35
|
+
# spelling.check_spelling
|
36
|
+
#
|
37
|
+
# @see HammyAssassin/danger-spelling
|
38
|
+
# @tags spelling, danger, pyspelling, hunspell, aspell
|
39
|
+
#
|
40
|
+
#
|
41
|
+
# @example execute pyspelling matrix with the name 'test_matrix' on all files modified or added in the given pull
|
42
|
+
# request, excluding some specific file names and excluding some words
|
43
|
+
# spelling.ignored_words = ["HammyAssassin"]
|
44
|
+
# spelling.ignored_files = ["Gemfile"]
|
45
|
+
# spelling.name = "test_matrix"
|
46
|
+
# spelling.check_spelling
|
47
|
+
#
|
48
|
+
# @see HammyAssassin/danger-spelling
|
49
|
+
# @tags spelling, danger, pyspelling, hunspell, aspell
|
50
|
+
#
|
51
|
+
class DangerSpelling < Plugin
|
52
|
+
# Allows you to ignore certain words that might otherwise be detected as a spelling error.
|
53
|
+
# default value is [] when its nil
|
54
|
+
#
|
55
|
+
# @return [Array<String>]
|
56
|
+
attr_accessor :ignored_words
|
57
|
+
|
58
|
+
# Allows you to ignore certain files that might otherwise be scanned by pyspelling.
|
59
|
+
# The default value is [] for when its nil
|
60
|
+
#
|
61
|
+
# @return [Array<String>]
|
62
|
+
attr_accessor :ignored_files
|
63
|
+
|
64
|
+
# **required** The name of the test matrix in your .pyspelling.yml
|
65
|
+
# An exception will be raised if this is not specified in your Danger file.
|
66
|
+
#
|
67
|
+
# @return [<String>]
|
68
|
+
attr_accessor :name
|
69
|
+
|
70
|
+
# Checks the spelling of all files added or modified in a given pull request. This will fail if
|
71
|
+
# pyspelling cannot be installed if not installed already. It will fail if `aspell` or `hunspell`
|
72
|
+
# are not detected.
|
73
|
+
#
|
74
|
+
# It will also fail if the required parameter `name` hasn't been specificed in the Danger file.
|
75
|
+
#
|
76
|
+
#
|
77
|
+
# @param [<Danger::FileList>] files **Optional** files to be scanned. Default value is nil. If nil, added and
|
78
|
+
# modified files will be scanned.
|
79
|
+
#
|
80
|
+
# @return [void]
|
81
|
+
#
|
82
|
+
def check_spelling(files = nil)
|
83
|
+
raise 'name must be a valid matrix name in your .pyspelling.yml.' if name.nil? || name.empty?
|
84
|
+
|
85
|
+
check_for_dependancies
|
86
|
+
|
87
|
+
new_files = get_files files
|
88
|
+
results_texts = pyspelling_results(new_files)
|
89
|
+
|
90
|
+
spell_issues = results_texts.select { |_, output| output.include? 'Spelling check failed' }
|
91
|
+
|
92
|
+
# Get some metadata about the local setup
|
93
|
+
current_slug = env.ci_source.repo_slug
|
94
|
+
|
95
|
+
update_message_for_issues(spell_issues, current_slug) if spell_issues.count.positive?
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
#
|
100
|
+
# **Internal Method**
|
101
|
+
#
|
102
|
+
# Updates the message that will eventually be posted as a comment a pull request with
|
103
|
+
# a new line for each time the spelling error has been detected.
|
104
|
+
#
|
105
|
+
# @param [<Hash>] spell_issues the Hash containing the file path & the detected mistakes.
|
106
|
+
# @param [<String>] current_slug the repo. eg /hamstringassassin/danger-spelling.
|
107
|
+
#
|
108
|
+
# @return [Array<String>] an array of messages to be displayed in the PR comment.
|
109
|
+
#
|
110
|
+
def update_message_for_issues(spell_issues, current_slug)
|
111
|
+
message = "### Spell Checker found issues\n\n"
|
112
|
+
|
113
|
+
spell_issues.each do |path, output|
|
114
|
+
git_loc = git_check(current_slug, path)
|
115
|
+
error_message = ''
|
116
|
+
error_message_updated = false
|
117
|
+
error_message = message_title(error_message, path, git_loc)
|
118
|
+
|
119
|
+
output_array = output.split(/\n/)
|
120
|
+
output_array = remove_ignored_words(output_array, path)
|
121
|
+
|
122
|
+
output_array.each do |txt|
|
123
|
+
File.open(path, 'r') do |file_handle|
|
124
|
+
file_handle.each_line do |path_line|
|
125
|
+
if find_word_in_text(path_line, txt)
|
126
|
+
error_message << "#{$INPUT_LINE_NUMBER} | #{txt} \n "
|
127
|
+
error_message_updated = true
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
if error_message_updated
|
133
|
+
message << error_message
|
134
|
+
error_message = ''
|
135
|
+
end
|
136
|
+
end
|
137
|
+
markdown message
|
138
|
+
end
|
139
|
+
|
140
|
+
#
|
141
|
+
#
|
142
|
+
# **Internal Method**
|
143
|
+
#
|
144
|
+
#
|
145
|
+
# appends the default message when a spelling error is found.
|
146
|
+
#
|
147
|
+
# @param [<String>] message the message to append
|
148
|
+
# @param [<String>] path the path of the file
|
149
|
+
# @param [<String>] git_loc the git location of the file
|
150
|
+
#
|
151
|
+
# @return [<String>] formatted message
|
152
|
+
#
|
153
|
+
def message_title(message, path, git_loc)
|
154
|
+
message << "#### [#{path}](#{git_loc})\n\n"
|
155
|
+
message << "Line | Typo |\n "
|
156
|
+
message << "| --- | ------ |\n "
|
157
|
+
message
|
158
|
+
end
|
159
|
+
|
160
|
+
#
|
161
|
+
#
|
162
|
+
# **Internal Method**
|
163
|
+
#
|
164
|
+
# splits a line of text up and checks if the spelling error is a match.
|
165
|
+
#
|
166
|
+
# @param [<String>] text the string to be split and checked.
|
167
|
+
# @param [<String>] word the word to find in text.
|
168
|
+
#
|
169
|
+
# @return [<Bool>] if the word is found.
|
170
|
+
#
|
171
|
+
def find_word_in_text(text, word)
|
172
|
+
val = false
|
173
|
+
line_array = text.split
|
174
|
+
line_array.each do |array_item|
|
175
|
+
array_item = array_item[0...array_item.size - 1] if array_item[-1] == '.'
|
176
|
+
# puts "array_item #{array_item}"
|
177
|
+
# puts "is url #{is_url(array_item.strip)}"
|
178
|
+
if array_item.strip == word.strip && !url?(array_item.strip)
|
179
|
+
val = true
|
180
|
+
val
|
181
|
+
end
|
182
|
+
end
|
183
|
+
val
|
184
|
+
end
|
185
|
+
|
186
|
+
#
|
187
|
+
#
|
188
|
+
# **Internal Method**
|
189
|
+
#
|
190
|
+
# checks if a given String is a URL
|
191
|
+
#
|
192
|
+
# @param [<String>] txt String to check
|
193
|
+
#
|
194
|
+
# @return [<Bool>]
|
195
|
+
#
|
196
|
+
def url?(txt)
|
197
|
+
txt =~ /\A#{URI::DEFAULT_PARSER.make_regexp}\z/
|
198
|
+
end
|
199
|
+
|
200
|
+
#
|
201
|
+
#
|
202
|
+
# **Internal Method**
|
203
|
+
#
|
204
|
+
# Runs pyspelling on the test matrix name provided with any files given.
|
205
|
+
#
|
206
|
+
# @param [<Danger::FileList>] new_files a list of files provided to scan with pyspelling.
|
207
|
+
#
|
208
|
+
# @return [Hash] returns a hash of the file scanned and any spelling errors found.
|
209
|
+
#
|
210
|
+
def pyspelling_results(new_files)
|
211
|
+
results_texts = {}
|
212
|
+
new_files.each do |file|
|
213
|
+
file_result = `pyspelling --name '#{name}' --source '#{file}'`
|
214
|
+
results_texts[file] = file_result
|
215
|
+
end
|
216
|
+
results_texts
|
217
|
+
end
|
218
|
+
|
219
|
+
#
|
220
|
+
#
|
221
|
+
# **Internal Method**
|
222
|
+
#
|
223
|
+
# Check on the git service used. Will raise an error if using bitbucket as it currently doesnt support that.
|
224
|
+
#
|
225
|
+
# @param [<String>] current_slug the current repo slug. eg. hamstringassassin/danger-spelling.
|
226
|
+
# @param [<String>] path path to file.
|
227
|
+
#
|
228
|
+
# @return [<String>] full path to file including branch.
|
229
|
+
#
|
230
|
+
def git_check(current_slug, path)
|
231
|
+
if defined? @dangerfile.github
|
232
|
+
"/#{current_slug}/tree/#{github.branch_for_head}/#{path}"
|
233
|
+
elsif defined? @dangerfile.gitlab
|
234
|
+
"/#{current_slug}/tree/#{gitlab.branch_for_head}/#{path}"
|
235
|
+
else
|
236
|
+
raise 'This plugin does not yet support bitbucket'
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
#
|
241
|
+
#
|
242
|
+
# **Internal Method**
|
243
|
+
#
|
244
|
+
# Check for dependencies. Raises exception if pyspelling, hunspell or aspell are not installed.
|
245
|
+
#
|
246
|
+
# @return [Void]
|
247
|
+
#
|
248
|
+
def check_for_dependancies
|
249
|
+
raise 'pyspelling is not in the users PATH, or it failed to install.' unless pyspelling_installed?
|
250
|
+
|
251
|
+
raise 'aspell or hunspell must be installed in order for pyspelling to work.' unless aspell_hunspell_installed?
|
252
|
+
end
|
253
|
+
|
254
|
+
#
|
255
|
+
#
|
256
|
+
# **Internal Method**
|
257
|
+
#
|
258
|
+
# Checks if a given line can be ignored if it contains expected pyspelling output.
|
259
|
+
#
|
260
|
+
# @param [<String>] text the text to check.
|
261
|
+
# @param [<String>] file_path the file path to check.
|
262
|
+
#
|
263
|
+
# @return [<Bool>] if the line can be ignored.
|
264
|
+
#
|
265
|
+
def ignore_line(text, file_path)
|
266
|
+
text.strip == 'Misspelled words:' ||
|
267
|
+
text.strip == "<text> #{file_path}" ||
|
268
|
+
text.strip == '!!!Spelling check failed!!!' ||
|
269
|
+
text.strip == '--------------------------------------------------------------------------------' ||
|
270
|
+
text.strip == ''
|
271
|
+
end
|
272
|
+
|
273
|
+
#
|
274
|
+
#
|
275
|
+
# **Internal Method**
|
276
|
+
#
|
277
|
+
# Removes some standard words in the pyspelling results.
|
278
|
+
# Words provided in `ignored_words` will also be removed from the results array.
|
279
|
+
#
|
280
|
+
# @param [<Array>] spelling_errors Complete list of spelling errors.
|
281
|
+
# @param [<String>] file_path file path.
|
282
|
+
#
|
283
|
+
# @return [<Array>] curated list of spelling errors, excluding standard and user defined words.
|
284
|
+
#
|
285
|
+
def remove_ignored_words(spelling_errors, file_path)
|
286
|
+
spelling_errors.delete('Misspelled words:')
|
287
|
+
spelling_errors.delete("<text> #{file_path}".strip)
|
288
|
+
spelling_errors.delete('!!!Spelling check failed!!!')
|
289
|
+
spelling_errors.delete('--------------------------------------------------------------------------------')
|
290
|
+
spelling_errors.delete('')
|
291
|
+
ignored_words.each do |word|
|
292
|
+
spelling_errors.delete(word)
|
293
|
+
end
|
294
|
+
spelling_errors
|
295
|
+
end
|
296
|
+
|
297
|
+
#
|
298
|
+
#
|
299
|
+
# **Internal Method**
|
300
|
+
#
|
301
|
+
# Checks of pyspelling is installed.
|
302
|
+
#
|
303
|
+
# @return [<Bool>]
|
304
|
+
#
|
305
|
+
def pyspelling_installed?
|
306
|
+
'which pyspelling'.strip.empty? == false
|
307
|
+
end
|
308
|
+
|
309
|
+
#
|
310
|
+
#
|
311
|
+
# **Internal Method**
|
312
|
+
#
|
313
|
+
# Checks if aspell is installed.
|
314
|
+
#
|
315
|
+
# @return [<Bool>]
|
316
|
+
#
|
317
|
+
def aspell_installed?
|
318
|
+
'which aspell'.strip.empty? == false
|
319
|
+
end
|
320
|
+
|
321
|
+
#
|
322
|
+
#
|
323
|
+
# **Internal Method**
|
324
|
+
#
|
325
|
+
# Checks if Hunspell is installed.
|
326
|
+
#
|
327
|
+
# @return [<Bool>]
|
328
|
+
#
|
329
|
+
def hunspell_installed?
|
330
|
+
'which hunspell'.strip.empty? == false
|
331
|
+
end
|
332
|
+
|
333
|
+
#
|
334
|
+
#
|
335
|
+
# **Internal Method**
|
336
|
+
#
|
337
|
+
# checks if aspell and hunspell are installed.
|
338
|
+
#
|
339
|
+
# @return [<Bool>]
|
340
|
+
#
|
341
|
+
def aspell_hunspell_installed?
|
342
|
+
aspell_installed? && hunspell_installed?
|
343
|
+
end
|
344
|
+
|
345
|
+
#
|
346
|
+
#
|
347
|
+
# **Internal Method**
|
348
|
+
#
|
349
|
+
# Gets a file list of the files provided or finds modified and added files to scan.
|
350
|
+
# If files are provided via `ignored_files` they will be removed from the final returned
|
351
|
+
# list.
|
352
|
+
#
|
353
|
+
# Will raise an exception if no files are found.
|
354
|
+
#
|
355
|
+
# @param [<Danger::FileList>] files FileList to scan. Can be nil.
|
356
|
+
#
|
357
|
+
# @return [<Danger::FileList>] a FileList of files found.
|
358
|
+
#
|
359
|
+
def get_files(files)
|
360
|
+
# Use either the files provided, or the modified & added files.
|
361
|
+
found_files = files ? Dir.glob(files) : (git.modified_files + git.added_files)
|
362
|
+
raise 'No files found to check' if found_files.nil?
|
363
|
+
|
364
|
+
ignored_files.each do |file|
|
365
|
+
found_files.delete(file)
|
366
|
+
end
|
367
|
+
found_files
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|