style-scanner 0.0.3
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.
- data/.gitignore +19 -0
- data/.rspec +0 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/Rakefile +16 -0
- data/bin/style +11 -0
- data/lib/dictionaries/acronyms.txt +5 -0
- data/lib/dictionaries/cliches.txt +680 -0
- data/lib/dictionaries/nationalities.txt +185 -0
- data/lib/style_scanner/problems/base.rb +45 -0
- data/lib/style_scanner/scanner.rb +67 -0
- data/lib/style_scanner/sentence.rb +61 -0
- data/lib/style_scanner/sentence_scans/adverb.rb +19 -0
- data/lib/style_scanner/sentence_scans/base.rb +70 -0
- data/lib/style_scanner/sentence_scans/broken_link.rb +27 -0
- data/lib/style_scanner/sentence_scans/capitalization.rb +57 -0
- data/lib/style_scanner/sentence_scans/cliche.rb +21 -0
- data/lib/style_scanner/sentence_scans/consecutively_repeated_word.rb +22 -0
- data/lib/style_scanner/sentence_scans/excess_white_space.rb +22 -0
- data/lib/style_scanner/sentence_scans/inappropriate_contraction.rb +20 -0
- data/lib/style_scanner/sentence_scans/latin_abbreviation.rb +38 -0
- data/lib/style_scanner/sentence_scans/passive_tense.rb +32 -0
- data/lib/style_scanner/sentence_scans/speaking_in_generalities.rb +17 -0
- data/lib/style_scanner/sentence_scans/spelling.rb +22 -0
- data/lib/style_scanner/sentence_scans/ugly_word.rb +17 -0
- data/lib/style_scanner/sentence_scans/used_word_already_in_sentence.rb +29 -0
- data/lib/style_scanner/sentence_scans/useless_word.rb +17 -0
- data/lib/style_scanner/string.rb +33 -0
- data/lib/style_scanner/tagged_word.rb +58 -0
- data/lib/style_scanner/tagger.rb +25 -0
- data/lib/style_scanner/version.rb +3 -0
- data/lib/style_scanner.rb +17 -0
- data/readme.textile +157 -0
- data/spec/fixtures/sample_text.txt +2 -0
- data/spec/fixtures/stylish/economist/economist-1.txt +29 -0
- data/spec/fixtures/stylish/economist/economist-2.txt +21 -0
- data/spec/fixtures/stylish/economist/economist-3.txt +9 -0
- data/spec/fixtures/stylish/economist/economist-4.txt +23 -0
- data/spec/fixtures/stylish/economist/economist-5.txt +15 -0
- data/spec/fixtures/stylish/economist/economist-6.txt +37 -0
- data/spec/integrations/command_line_spec.rb +41 -0
- data/spec/problems/base_spec.rb +38 -0
- data/spec/scanner_spec.rb +41 -0
- data/spec/sentence_scans/adverb_spec.rb +13 -0
- data/spec/sentence_scans/base_spec.rb +18 -0
- data/spec/sentence_scans/broken_link_spec.rb +18 -0
- data/spec/sentence_scans/capitalization_spec.rb +44 -0
- data/spec/sentence_scans/cliche_spec.rb +35 -0
- data/spec/sentence_scans/consecutively_repeated_word_spec.rb +26 -0
- data/spec/sentence_scans/excess_white_space_spec.rb +22 -0
- data/spec/sentence_scans/inappropriate_contraction_spec.rb +21 -0
- data/spec/sentence_scans/latin_abbreviation_spec.rb +34 -0
- data/spec/sentence_scans/passive_tense_spec.rb +138 -0
- data/spec/sentence_scans/speaking_in_generalities_spec.rb +15 -0
- data/spec/sentence_scans/spelling_spec.rb +16 -0
- data/spec/sentence_scans/ugly_word_spec.rb +29 -0
- data/spec/sentence_scans/used_word_already_in_sentence.rb +21 -0
- data/spec/sentence_scans/useless_word_spec.rb +14 -0
- data/spec/sentence_spec.rb +76 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/string_spec.rb +30 -0
- data/spec/tagged_word_spec.rb +35 -0
- data/spec/tagger_spec.rb +14 -0
- data/style-scanner.gemspec +30 -0
- metadata +263 -0
@@ -0,0 +1,185 @@
|
|
1
|
+
afghans
|
2
|
+
albanians
|
3
|
+
algerians
|
4
|
+
americans
|
5
|
+
andorrans
|
6
|
+
angolans
|
7
|
+
argentines
|
8
|
+
armenians
|
9
|
+
aromanians
|
10
|
+
arubans
|
11
|
+
australians
|
12
|
+
albanians
|
13
|
+
algerians
|
14
|
+
andorrans
|
15
|
+
angolans
|
16
|
+
argentines
|
17
|
+
armenians
|
18
|
+
aromanians
|
19
|
+
arubans
|
20
|
+
australians
|
21
|
+
austrians
|
22
|
+
azeris
|
23
|
+
bahamians
|
24
|
+
bahrainis
|
25
|
+
bangladeshis
|
26
|
+
barbadians
|
27
|
+
belarusians
|
28
|
+
belgians
|
29
|
+
belizeans
|
30
|
+
bermudians
|
31
|
+
boers
|
32
|
+
bosnians
|
33
|
+
brazilians
|
34
|
+
bretons
|
35
|
+
britons
|
36
|
+
british virgin islanders
|
37
|
+
bulgarians
|
38
|
+
burkinabès
|
39
|
+
burundians
|
40
|
+
cambodians
|
41
|
+
cameroonians
|
42
|
+
canadians
|
43
|
+
catalans
|
44
|
+
cape verdeans
|
45
|
+
chadians
|
46
|
+
chileans
|
47
|
+
colombians
|
48
|
+
comorians
|
49
|
+
congolese
|
50
|
+
croatians
|
51
|
+
cubans
|
52
|
+
cypriots
|
53
|
+
turkish cypriots
|
54
|
+
czechs
|
55
|
+
danes
|
56
|
+
dominicans
|
57
|
+
dominicans
|
58
|
+
dutch
|
59
|
+
east timorese
|
60
|
+
ecuadorians
|
61
|
+
egyptians
|
62
|
+
emiratis
|
63
|
+
english
|
64
|
+
eritreans
|
65
|
+
estonians
|
66
|
+
ethiopians
|
67
|
+
finns
|
68
|
+
finnish swedish
|
69
|
+
fijians
|
70
|
+
filipinos
|
71
|
+
french citizens
|
72
|
+
georgians
|
73
|
+
germans
|
74
|
+
baltic germans
|
75
|
+
ghanaians
|
76
|
+
gibraltar
|
77
|
+
greeks
|
78
|
+
grenadians
|
79
|
+
guatemalans
|
80
|
+
guianese
|
81
|
+
guineans
|
82
|
+
guinea-bissau nationals
|
83
|
+
guyanese
|
84
|
+
haitians
|
85
|
+
hondurans
|
86
|
+
hong kongers
|
87
|
+
hungarians
|
88
|
+
icelanders
|
89
|
+
indians
|
90
|
+
indonesians
|
91
|
+
iranians
|
92
|
+
iraqis
|
93
|
+
irish
|
94
|
+
israelis
|
95
|
+
italians
|
96
|
+
ivoirians
|
97
|
+
jamaicans
|
98
|
+
japanese
|
99
|
+
jordanians
|
100
|
+
kazakhs
|
101
|
+
kenyans
|
102
|
+
koreans
|
103
|
+
kosovo albanians
|
104
|
+
kuwaitis
|
105
|
+
lao
|
106
|
+
latvians
|
107
|
+
lebanese
|
108
|
+
liberians
|
109
|
+
libyans
|
110
|
+
liechtensteiners
|
111
|
+
lithuanians
|
112
|
+
luxembourgers
|
113
|
+
macedonians
|
114
|
+
malawians
|
115
|
+
malaysians
|
116
|
+
maldivians
|
117
|
+
malians
|
118
|
+
maltese
|
119
|
+
manx
|
120
|
+
mauritians
|
121
|
+
mexicans
|
122
|
+
moldovans
|
123
|
+
moroccans
|
124
|
+
mongolians
|
125
|
+
montenegrins
|
126
|
+
mozambicans
|
127
|
+
namibians
|
128
|
+
nepalese
|
129
|
+
new zealanders
|
130
|
+
nicaraguans
|
131
|
+
nigeriens
|
132
|
+
nigerians
|
133
|
+
norwegians
|
134
|
+
pakistanis
|
135
|
+
palauans
|
136
|
+
palestinians
|
137
|
+
panamanians
|
138
|
+
papua new guineans
|
139
|
+
paraguayans
|
140
|
+
peruvians
|
141
|
+
poles
|
142
|
+
portuguese
|
143
|
+
puerto ricans
|
144
|
+
quebecers
|
145
|
+
réunionnais
|
146
|
+
romanians
|
147
|
+
russians
|
148
|
+
baltic russians
|
149
|
+
rwandans
|
150
|
+
salvadorans
|
151
|
+
são tomé and príncipe
|
152
|
+
saudis
|
153
|
+
scots
|
154
|
+
senegalese
|
155
|
+
serbs
|
156
|
+
sierra leoneans
|
157
|
+
sikhs
|
158
|
+
singaporeans
|
159
|
+
slovaks
|
160
|
+
slovenes
|
161
|
+
somalis
|
162
|
+
south africans
|
163
|
+
spaniards
|
164
|
+
sri lankans
|
165
|
+
sudanese
|
166
|
+
swedes
|
167
|
+
swiss
|
168
|
+
syrians
|
169
|
+
taiwanese
|
170
|
+
tanzanians
|
171
|
+
thais
|
172
|
+
tibetans
|
173
|
+
tobagonians
|
174
|
+
trinidadians
|
175
|
+
turks
|
176
|
+
tuvaluans
|
177
|
+
ugandans
|
178
|
+
ukrainians
|
179
|
+
uruguayans
|
180
|
+
venezuelans
|
181
|
+
vietnamese
|
182
|
+
welsh
|
183
|
+
yemenis
|
184
|
+
zambians
|
185
|
+
zimbabweans
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
module StyleScanner
|
3
|
+
module Problems
|
4
|
+
class Base
|
5
|
+
attr_reader :offending_text, :sentence
|
6
|
+
|
7
|
+
def initialize(sentence, offending_text)
|
8
|
+
@sentence = sentence
|
9
|
+
@offending_text = offending_text
|
10
|
+
end
|
11
|
+
|
12
|
+
def on_text?(problematic_word)
|
13
|
+
offending_text.strip_punctuation == problematic_word.strip_punctuation
|
14
|
+
end
|
15
|
+
|
16
|
+
def user_friendly_readout
|
17
|
+
[problem_name.red,sentence.text.green,offending_text.yellow].join(" | ")
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def problem_name
|
23
|
+
unformatted_name = self.class.to_s.gsub(/Style::Problems::/,"").titlecase
|
24
|
+
return "Cliché" if unformatted_name == "Cliche"
|
25
|
+
unformatted_name
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.problem_class_names_from_dir
|
31
|
+
Dir[(File.dirname(__FILE__) + "/../sentence_scans/*.rb")].
|
32
|
+
map {|filename| File.basename(filename, ".rb").split("_").map(&:capitalize).join } - ["Base"]
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.dynamically_generate_problem_classes
|
36
|
+
problem_class_names_from_dir.each do |problem_class_name|
|
37
|
+
eval %Q{ class #{problem_class_name} < Base
|
38
|
+
end}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
dynamically_generate_problem_classes
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module StyleScanner
|
2
|
+
class Scanner
|
3
|
+
|
4
|
+
attr_reader :input_text, :sentences, :options
|
5
|
+
attr_accessor :finished_text
|
6
|
+
|
7
|
+
def initialize(input, options={})
|
8
|
+
# remove html
|
9
|
+
@options = options
|
10
|
+
@input_text = convert_to_txt(input)
|
11
|
+
@sentences = split_into_sentences
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
def scan
|
16
|
+
sentences.each do |sentence|
|
17
|
+
desired_scans.each do |scanner_type|
|
18
|
+
scanner_type.scan(sentence)
|
19
|
+
end
|
20
|
+
puts sentence.user_friendly_readout
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def convert_to_txt(input)
|
27
|
+
if options[:html]
|
28
|
+
remove_html(input)
|
29
|
+
elsif options[:textile]
|
30
|
+
textile_to_txt(input)
|
31
|
+
else
|
32
|
+
input
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def textile_to_txt(input)
|
37
|
+
remove_html(RedCloth.new(input).to_html)
|
38
|
+
end
|
39
|
+
|
40
|
+
def remove_html(input)
|
41
|
+
Sanitize.clean(input)
|
42
|
+
end
|
43
|
+
|
44
|
+
def desired_scans
|
45
|
+
desired_optional_scans + default_scans
|
46
|
+
end
|
47
|
+
|
48
|
+
def desired_optional_scans
|
49
|
+
result = []
|
50
|
+
result << SentenceScans::Adverb if options[:adverb]
|
51
|
+
result << SentenceScans::Spelling if options[:spellcheck]
|
52
|
+
result
|
53
|
+
end
|
54
|
+
|
55
|
+
def split_into_sentences
|
56
|
+
tokenizer = Punkt::SentenceTokenizer.new(input_text)
|
57
|
+
tokenizer.sentences_from_text(input_text, :output => :sentences_text).map {|text| Sentence.new(text)}
|
58
|
+
end
|
59
|
+
|
60
|
+
def default_scans
|
61
|
+
[SentenceScans::UselessWord, SentenceScans::UglyWord, SentenceScans::ConsecutivelyRepeatedWord,
|
62
|
+
SentenceScans::ExcessWhiteSpace, SentenceScans::BrokenLink, SentenceScans::UsedWordAlreadyInSentence,
|
63
|
+
SentenceScans::SpeakingInGeneralities, SentenceScans::Cliche, SentenceScans::PassiveTense, SentenceScans::Capitalization]
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module StyleScanner
|
2
|
+
class Sentence
|
3
|
+
|
4
|
+
extend Forwardable
|
5
|
+
def_delegators :copy_of_text, :gsub!, :match, :scan, :downcase, :sub!
|
6
|
+
|
7
|
+
attr_reader :problems, :text
|
8
|
+
|
9
|
+
def initialize(text)
|
10
|
+
@text = text
|
11
|
+
@problems = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def find_problems_by_type(problem_type)
|
15
|
+
@problems.select {|problem| problem.class == problem_type}
|
16
|
+
end
|
17
|
+
|
18
|
+
def tagged_words
|
19
|
+
@tagged_words ||= Tagger.new(text).tagged_words
|
20
|
+
end
|
21
|
+
|
22
|
+
def adverbs
|
23
|
+
part_of_speech("RB")
|
24
|
+
end
|
25
|
+
|
26
|
+
def contains?(word, options = {})
|
27
|
+
options = {:strip_case=> true}.merge(options)
|
28
|
+
text_to_scan = text
|
29
|
+
text_to_scan = text_to_scan.downcase if options[:strip_case]
|
30
|
+
text_to_scan = text_to_scan.stem_verbs if options[:stem_verbs]
|
31
|
+
text_to_scan.match /\b#{word}\b/
|
32
|
+
end
|
33
|
+
|
34
|
+
def user_friendly_readout
|
35
|
+
problems.flatten.map(&:user_friendly_readout) if with_problems?
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_problem(problem)
|
39
|
+
problems << problem
|
40
|
+
end
|
41
|
+
|
42
|
+
def with_problems?
|
43
|
+
problems.any?
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_s
|
47
|
+
"Sentence Obj: text: #{text} problems: #{problems}"
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def part_of_speech(pos)
|
53
|
+
tagged_words.select {|tagged_word| tagged_word.tag == pos }.map(&:word)
|
54
|
+
end
|
55
|
+
|
56
|
+
# we don't want to modify the original text
|
57
|
+
def copy_of_text
|
58
|
+
text.dup
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module StyleScanner
|
2
|
+
module SentenceScans
|
3
|
+
class Base
|
4
|
+
|
5
|
+
attr_reader :sentence
|
6
|
+
|
7
|
+
def initialize(sentence)
|
8
|
+
@sentence = sentence
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.scan(sentence)
|
12
|
+
new(sentence).scan
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def word_pairs
|
18
|
+
# ruby searches for WORD_PAIRS on base class without the following line
|
19
|
+
word_pairs = self.class::WORD_PAIRS
|
20
|
+
end
|
21
|
+
|
22
|
+
def replacement_word(offending_word)
|
23
|
+
word_pairs[offending_word]
|
24
|
+
end
|
25
|
+
|
26
|
+
def tokenized_words
|
27
|
+
words.map(&:tokenized).reject {|word| word == ""}
|
28
|
+
end
|
29
|
+
|
30
|
+
# We retokenize for the text case where no overall scanner is prepared
|
31
|
+
def words
|
32
|
+
sentence.tagged_words
|
33
|
+
end
|
34
|
+
|
35
|
+
def next_word(word)
|
36
|
+
words.at(words.index(word) + 1)
|
37
|
+
end
|
38
|
+
|
39
|
+
def next_significant_word(word)
|
40
|
+
possible_word = next_word(word)
|
41
|
+
return next_significant_word(possible_word) if possible_word.adverb? || possible_word.preposition? || possible_word.determiner?
|
42
|
+
possible_word
|
43
|
+
end
|
44
|
+
|
45
|
+
def already_has_that_problem_on_text(offending_text)
|
46
|
+
sentence.find_problems_by_type(problem_class).any? do |problem|
|
47
|
+
problem.on_text?(offending_text)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def problem_class
|
52
|
+
Problems.const_get(self.class.to_s.gsub("StyleScanner::SentenceScans::", ""))
|
53
|
+
end
|
54
|
+
|
55
|
+
def create_problem(offending_text)
|
56
|
+
sentence.add_problem(problem_class.new(sentence, offending_text)) unless already_has_that_problem_on_text(offending_text)
|
57
|
+
end
|
58
|
+
|
59
|
+
class << self
|
60
|
+
|
61
|
+
def load_file(filename)
|
62
|
+
file_location = File.expand_path("../../../dictionaries/#{filename}", __FILE__)
|
63
|
+
IO.read(file_location).split("\n")
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module StyleScanner
|
2
|
+
module SentenceScans
|
3
|
+
class BrokenLink < Base
|
4
|
+
|
5
|
+
URL_REGEX = /(?#Protocol)(?:(?:ht|f)tp(?:s?)\:\/\/|~\/|\/)?(?#Username:Password)(?:\w+:\w+@)?(?#Subdomains)(?:(?:[-\w]+\.)+(?#TopLevel Domains)(?:com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum|travel|[a-z]{2}))(?#Port)(?::[\d]{1,5})?(?#Directories)(?:(?:(?:\/(?:[-\w~!$+|.,=]|%[a-f\d]{2})+)+|\/)+|\?|#)?(?#Query)(?:(?:\?(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)(?:&(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)*)*(?#Anchor)(?:#(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)?/
|
6
|
+
|
7
|
+
def scan
|
8
|
+
links = sentence.scan(URL_REGEX)
|
9
|
+
links.each do |url|
|
10
|
+
begin
|
11
|
+
attempt_to_visit_url(url)
|
12
|
+
# socket error occurs if link is bad
|
13
|
+
rescue SocketError, Errno::ECONNREFUSED
|
14
|
+
create_problem("Url #{url} does not work")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def attempt_to_visit_url(url)
|
22
|
+
Net::HTTP.get_response(URI.parse(url))
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module StyleScanner
|
2
|
+
module SentenceScans
|
3
|
+
class Capitalization < Base
|
4
|
+
|
5
|
+
ACRONYMS = load_file("acronyms.txt")
|
6
|
+
NATIONALITIES = load_file("nationalities.txt")
|
7
|
+
MONTHS = %w(
|
8
|
+
january february march april may june july august september october november december
|
9
|
+
)
|
10
|
+
DAYS = %w(
|
11
|
+
monday tuesday wednesday thursday friday saturday sunday
|
12
|
+
)
|
13
|
+
SEASONS = %w(
|
14
|
+
Winter
|
15
|
+
Summer
|
16
|
+
Spring
|
17
|
+
Autumn
|
18
|
+
)
|
19
|
+
|
20
|
+
def scan
|
21
|
+
flag_lowercase(MONTHS)
|
22
|
+
flag_lowercase(ACRONYMS)
|
23
|
+
flag_lowercase(DAYS)
|
24
|
+
flag_lowercase(NATIONALITIES)
|
25
|
+
flag_uppercase(SEASONS)
|
26
|
+
create_problem(first_letter) if first_letter_is_lowercase?
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def first_word
|
32
|
+
words.first
|
33
|
+
end
|
34
|
+
|
35
|
+
def first_letter
|
36
|
+
first_word.word.chars.first
|
37
|
+
end
|
38
|
+
|
39
|
+
def first_letter_is_lowercase?
|
40
|
+
first_letter != first_letter.upcase
|
41
|
+
end
|
42
|
+
|
43
|
+
def flag_uppercase(collection)
|
44
|
+
collection.each do |word|
|
45
|
+
create_problem(word.downcase) if sentence.contains?(word, :strip_case => false)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def flag_lowercase(collection)
|
50
|
+
collection.each do |word|
|
51
|
+
create_problem(word.upcase) if sentence.contains?(word, :strip_case => false)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module StyleScanner
|
2
|
+
module SentenceScans
|
3
|
+
class Cliche < Base
|
4
|
+
|
5
|
+
CLICHES = load_file("cliches.txt")
|
6
|
+
|
7
|
+
def scan
|
8
|
+
Cliche.stemmed_cliches.each.with_index do |cliche, index|
|
9
|
+
create_problem(CLICHES[index]) if sentence.contains?(cliche, :stem_verbs => true)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.stemmed_cliches
|
14
|
+
@@stemmed_cliches ||= CLICHES.map do |cliche|
|
15
|
+
cliche.stem_verbs
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module StyleScanner
|
2
|
+
module SentenceScans
|
3
|
+
class ConsecutivelyRepeatedWord < Base
|
4
|
+
|
5
|
+
REPEATED_WORD_REGEX = /\b(\w+)\b\s+\b\1\b/
|
6
|
+
|
7
|
+
def scan
|
8
|
+
consecutively_repeated_words.each do |repeated_word|
|
9
|
+
create_problem("#{repeated_word} #{repeated_word}")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def consecutively_repeated_words
|
16
|
+
sentence.downcase.scan(REPEATED_WORD_REGEX).flatten
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module StyleScanner
|
2
|
+
module SentenceScans
|
3
|
+
class ExcessWhiteSpace < Base
|
4
|
+
|
5
|
+
def scan
|
6
|
+
white_space_problems.each do |problem|
|
7
|
+
create_problem(problem.post_match)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def white_space_problems
|
14
|
+
between_words = sentence.match /\s{2,}/
|
15
|
+
before_full_stop = sentence.match /\s{1,}\./
|
16
|
+
before_commas = sentence.match /\s{1,}\,/
|
17
|
+
[between_words, before_full_stop, before_commas].compact.flatten
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module StyleScanner
|
2
|
+
module SentenceScans
|
3
|
+
class InappropriateContraction < Base
|
4
|
+
|
5
|
+
WORD_PAIRS = {"don't" => "do not", "can't" => 'cannot',
|
6
|
+
"won't" => "will not", "shan't" => "shall not",
|
7
|
+
"hasn't" => "has not", "i'm" => "I am", "he'll" => "he will",
|
8
|
+
"she'll" => "she will", "didn't" => "did not",
|
9
|
+
"shouldn't" => "should not", "could've" => "could have",
|
10
|
+
"they'll" => "they will", "we'll" => "we will"}
|
11
|
+
|
12
|
+
def scan
|
13
|
+
WORD_PAIRS.keys.each do |offender|
|
14
|
+
create_problem(replacement_word(offender)) if sentence.contains?(offender)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module StyleScanner
|
2
|
+
module SentenceScans
|
3
|
+
class LatinAbbreviation < Base
|
4
|
+
|
5
|
+
LATINS = ["ie", "etc", "cf", "et cetera", "ergo",
|
6
|
+
"c", "cf", "ibid", "dto", "et al", "et seq", "vs",
|
7
|
+
"re", "nb"]
|
8
|
+
|
9
|
+
def scan
|
10
|
+
dot_placement_permutations(LATINS).each do |latin_abbreviation|
|
11
|
+
if sentence.contains?(latin_abbreviation)
|
12
|
+
create_problem(latin_abbreviation)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def dot_placement_permutations(abbreviations)
|
20
|
+
abbreviations.map do |abbr|
|
21
|
+
[abbr, dot_at_end(abbr), dot_between_every_letter(abbr)]
|
22
|
+
end.flatten
|
23
|
+
end
|
24
|
+
|
25
|
+
def dot_at_end(abbr)
|
26
|
+
"#{abbr}."
|
27
|
+
end
|
28
|
+
|
29
|
+
# this method overshoots in permuations for some latins, but it
|
30
|
+
# shouldn't matter
|
31
|
+
def dot_between_every_letter(abbr)
|
32
|
+
dot_between_all_but_last = abbr.split("").join(".")
|
33
|
+
[dot_between_all_but_last, dot_at_end(dot_between_all_but_last)]
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module StyleScanner
|
2
|
+
module SentenceScans
|
3
|
+
class PassiveTense < Base
|
4
|
+
|
5
|
+
# heuristic: A "BE" verb follwed by a verb other than a gerund
|
6
|
+
def scan
|
7
|
+
passives.each do |passive|
|
8
|
+
create_problem(word_in_context(passive))
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def passives
|
15
|
+
words.find_all do |word|
|
16
|
+
word.be_verb? && (! next_word(word).gerund_verb? ) && (! state_word?(word))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def word_in_context(main_word)
|
21
|
+
position_of_main_word = words.index(main_word)
|
22
|
+
words[position_of_main_word-1, 3].map(&:word).join(" ")
|
23
|
+
end
|
24
|
+
|
25
|
+
def state_word?(word)
|
26
|
+
word = next_significant_word(word)
|
27
|
+
word.noun? || word.possessive?
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|