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.
Files changed (65) hide show
  1. data/.gitignore +19 -0
  2. data/.rspec +0 -0
  3. data/.rvmrc +1 -0
  4. data/Gemfile +4 -0
  5. data/Rakefile +16 -0
  6. data/bin/style +11 -0
  7. data/lib/dictionaries/acronyms.txt +5 -0
  8. data/lib/dictionaries/cliches.txt +680 -0
  9. data/lib/dictionaries/nationalities.txt +185 -0
  10. data/lib/style_scanner/problems/base.rb +45 -0
  11. data/lib/style_scanner/scanner.rb +67 -0
  12. data/lib/style_scanner/sentence.rb +61 -0
  13. data/lib/style_scanner/sentence_scans/adverb.rb +19 -0
  14. data/lib/style_scanner/sentence_scans/base.rb +70 -0
  15. data/lib/style_scanner/sentence_scans/broken_link.rb +27 -0
  16. data/lib/style_scanner/sentence_scans/capitalization.rb +57 -0
  17. data/lib/style_scanner/sentence_scans/cliche.rb +21 -0
  18. data/lib/style_scanner/sentence_scans/consecutively_repeated_word.rb +22 -0
  19. data/lib/style_scanner/sentence_scans/excess_white_space.rb +22 -0
  20. data/lib/style_scanner/sentence_scans/inappropriate_contraction.rb +20 -0
  21. data/lib/style_scanner/sentence_scans/latin_abbreviation.rb +38 -0
  22. data/lib/style_scanner/sentence_scans/passive_tense.rb +32 -0
  23. data/lib/style_scanner/sentence_scans/speaking_in_generalities.rb +17 -0
  24. data/lib/style_scanner/sentence_scans/spelling.rb +22 -0
  25. data/lib/style_scanner/sentence_scans/ugly_word.rb +17 -0
  26. data/lib/style_scanner/sentence_scans/used_word_already_in_sentence.rb +29 -0
  27. data/lib/style_scanner/sentence_scans/useless_word.rb +17 -0
  28. data/lib/style_scanner/string.rb +33 -0
  29. data/lib/style_scanner/tagged_word.rb +58 -0
  30. data/lib/style_scanner/tagger.rb +25 -0
  31. data/lib/style_scanner/version.rb +3 -0
  32. data/lib/style_scanner.rb +17 -0
  33. data/readme.textile +157 -0
  34. data/spec/fixtures/sample_text.txt +2 -0
  35. data/spec/fixtures/stylish/economist/economist-1.txt +29 -0
  36. data/spec/fixtures/stylish/economist/economist-2.txt +21 -0
  37. data/spec/fixtures/stylish/economist/economist-3.txt +9 -0
  38. data/spec/fixtures/stylish/economist/economist-4.txt +23 -0
  39. data/spec/fixtures/stylish/economist/economist-5.txt +15 -0
  40. data/spec/fixtures/stylish/economist/economist-6.txt +37 -0
  41. data/spec/integrations/command_line_spec.rb +41 -0
  42. data/spec/problems/base_spec.rb +38 -0
  43. data/spec/scanner_spec.rb +41 -0
  44. data/spec/sentence_scans/adverb_spec.rb +13 -0
  45. data/spec/sentence_scans/base_spec.rb +18 -0
  46. data/spec/sentence_scans/broken_link_spec.rb +18 -0
  47. data/spec/sentence_scans/capitalization_spec.rb +44 -0
  48. data/spec/sentence_scans/cliche_spec.rb +35 -0
  49. data/spec/sentence_scans/consecutively_repeated_word_spec.rb +26 -0
  50. data/spec/sentence_scans/excess_white_space_spec.rb +22 -0
  51. data/spec/sentence_scans/inappropriate_contraction_spec.rb +21 -0
  52. data/spec/sentence_scans/latin_abbreviation_spec.rb +34 -0
  53. data/spec/sentence_scans/passive_tense_spec.rb +138 -0
  54. data/spec/sentence_scans/speaking_in_generalities_spec.rb +15 -0
  55. data/spec/sentence_scans/spelling_spec.rb +16 -0
  56. data/spec/sentence_scans/ugly_word_spec.rb +29 -0
  57. data/spec/sentence_scans/used_word_already_in_sentence.rb +21 -0
  58. data/spec/sentence_scans/useless_word_spec.rb +14 -0
  59. data/spec/sentence_spec.rb +76 -0
  60. data/spec/spec_helper.rb +26 -0
  61. data/spec/string_spec.rb +30 -0
  62. data/spec/tagged_word_spec.rb +35 -0
  63. data/spec/tagger_spec.rb +14 -0
  64. data/style-scanner.gemspec +30 -0
  65. 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,19 @@
1
+ module StyleScanner
2
+ module SentenceScans
3
+ class Adverb < Base
4
+
5
+ def scan
6
+ adverbs.each do |adverb|
7
+ create_problem(adverb)
8
+ end
9
+ end
10
+
11
+ private
12
+
13
+ def adverbs
14
+ sentence.adverbs
15
+ end
16
+
17
+ end
18
+ end
19
+ 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