wordlist 0.1.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ruby.yml +27 -0
  3. data/.gitignore +6 -3
  4. data/ChangeLog.md +45 -1
  5. data/Gemfile +13 -0
  6. data/LICENSE.txt +1 -3
  7. data/README.md +266 -61
  8. data/Rakefile +7 -32
  9. data/benchmarks.rb +115 -0
  10. data/bin/wordlist +4 -7
  11. data/data/stop_words/ar.txt +104 -0
  12. data/data/stop_words/bg.txt +259 -0
  13. data/data/stop_words/bn.txt +363 -0
  14. data/data/stop_words/ca.txt +126 -0
  15. data/data/stop_words/cs.txt +138 -0
  16. data/data/stop_words/da.txt +101 -0
  17. data/data/stop_words/de.txt +129 -0
  18. data/data/stop_words/el.txt +79 -0
  19. data/data/stop_words/en.txt +175 -0
  20. data/data/stop_words/es.txt +178 -0
  21. data/data/stop_words/eu.txt +98 -0
  22. data/data/stop_words/fa.txt +332 -0
  23. data/data/stop_words/fi.txt +747 -0
  24. data/data/stop_words/fr.txt +116 -0
  25. data/data/stop_words/ga.txt +109 -0
  26. data/data/stop_words/gl.txt +160 -0
  27. data/data/stop_words/he.txt +499 -0
  28. data/data/stop_words/hi.txt +97 -0
  29. data/data/stop_words/hr.txt +179 -0
  30. data/data/stop_words/hu.txt +35 -0
  31. data/data/stop_words/hy.txt +45 -0
  32. data/data/stop_words/id.txt +357 -0
  33. data/data/stop_words/it.txt +134 -0
  34. data/data/stop_words/ja.txt +44 -0
  35. data/data/stop_words/ko.txt +677 -0
  36. data/data/stop_words/ku.txt +63 -0
  37. data/data/stop_words/lt.txt +507 -0
  38. data/data/stop_words/lv.txt +163 -0
  39. data/data/stop_words/mr.txt +99 -0
  40. data/data/stop_words/nl.txt +48 -0
  41. data/data/stop_words/no.txt +172 -0
  42. data/data/stop_words/pl.txt +138 -0
  43. data/data/stop_words/pt.txt +147 -0
  44. data/data/stop_words/ro.txt +281 -0
  45. data/data/stop_words/ru.txt +421 -0
  46. data/data/stop_words/sk.txt +173 -0
  47. data/data/stop_words/sv.txt +386 -0
  48. data/data/stop_words/th.txt +115 -0
  49. data/data/stop_words/tr.txt +114 -0
  50. data/data/stop_words/uk.txt +28 -0
  51. data/data/stop_words/ur.txt +513 -0
  52. data/data/stop_words/zh.txt +125 -0
  53. data/gemspec.yml +4 -10
  54. data/lib/wordlist/abstract_wordlist.rb +24 -0
  55. data/lib/wordlist/builder.rb +170 -138
  56. data/lib/wordlist/cli.rb +458 -0
  57. data/lib/wordlist/compression/reader.rb +72 -0
  58. data/lib/wordlist/compression/writer.rb +80 -0
  59. data/lib/wordlist/exceptions.rb +31 -0
  60. data/lib/wordlist/file.rb +176 -0
  61. data/lib/wordlist/format.rb +38 -0
  62. data/lib/wordlist/lexer/lang.rb +32 -0
  63. data/lib/wordlist/lexer/stop_words.rb +68 -0
  64. data/lib/wordlist/lexer.rb +218 -0
  65. data/lib/wordlist/list_methods.rb +462 -0
  66. data/lib/wordlist/modifiers/capitalize.rb +45 -0
  67. data/lib/wordlist/modifiers/downcase.rb +45 -0
  68. data/lib/wordlist/modifiers/gsub.rb +51 -0
  69. data/lib/wordlist/modifiers/modifier.rb +44 -0
  70. data/lib/wordlist/modifiers/mutate.rb +133 -0
  71. data/lib/wordlist/modifiers/mutate_case.rb +25 -0
  72. data/lib/wordlist/modifiers/sub.rb +97 -0
  73. data/lib/wordlist/modifiers/tr.rb +71 -0
  74. data/lib/wordlist/modifiers/upcase.rb +45 -0
  75. data/lib/wordlist/modifiers.rb +8 -0
  76. data/lib/wordlist/operators/binary_operator.rb +38 -0
  77. data/lib/wordlist/operators/concat.rb +47 -0
  78. data/lib/wordlist/operators/intersect.rb +55 -0
  79. data/lib/wordlist/operators/operator.rb +29 -0
  80. data/lib/wordlist/operators/power.rb +72 -0
  81. data/lib/wordlist/operators/product.rb +50 -0
  82. data/lib/wordlist/operators/subtract.rb +54 -0
  83. data/lib/wordlist/operators/unary_operator.rb +29 -0
  84. data/lib/wordlist/operators/union.rb +61 -0
  85. data/lib/wordlist/operators/unique.rb +52 -0
  86. data/lib/wordlist/operators.rb +7 -0
  87. data/lib/wordlist/unique_filter.rb +40 -61
  88. data/lib/wordlist/version.rb +1 -1
  89. data/lib/wordlist/words.rb +71 -0
  90. data/lib/wordlist.rb +103 -2
  91. data/spec/abstract_list_spec.rb +18 -0
  92. data/spec/builder_spec.rb +220 -76
  93. data/spec/cli_spec.rb +801 -0
  94. data/spec/compression/reader_spec.rb +137 -0
  95. data/spec/compression/writer_spec.rb +194 -0
  96. data/spec/file_spec.rb +258 -0
  97. data/spec/fixtures/wordlist.txt +15 -0
  98. data/spec/fixtures/wordlist.txt.bz2 +0 -0
  99. data/spec/fixtures/wordlist.txt.gz +0 -0
  100. data/spec/fixtures/wordlist.txt.xz +0 -0
  101. data/spec/fixtures/wordlist_with_ambiguous_format +3 -0
  102. data/spec/fixtures/wordlist_with_comments.txt +19 -0
  103. data/spec/fixtures/wordlist_with_empty_lines.txt +19 -0
  104. data/spec/format_spec.rb +50 -0
  105. data/spec/helpers/text.rb +3 -3
  106. data/spec/helpers/wordlist.rb +2 -2
  107. data/spec/lexer/lang_spec.rb +70 -0
  108. data/spec/lexer/stop_words_spec.rb +77 -0
  109. data/spec/lexer_spec.rb +652 -0
  110. data/spec/list_methods_spec.rb +181 -0
  111. data/spec/modifiers/capitalize_spec.rb +27 -0
  112. data/spec/modifiers/downcase_spec.rb +27 -0
  113. data/spec/modifiers/gsub_spec.rb +59 -0
  114. data/spec/modifiers/modifier_spec.rb +20 -0
  115. data/spec/modifiers/mutate_case_spec.rb +46 -0
  116. data/spec/modifiers/mutate_spec.rb +39 -0
  117. data/spec/modifiers/sub_spec.rb +98 -0
  118. data/spec/modifiers/tr_spec.rb +46 -0
  119. data/spec/modifiers/upcase_spec.rb +27 -0
  120. data/spec/operators/binary_operator_spec.rb +19 -0
  121. data/spec/operators/concat_spec.rb +26 -0
  122. data/spec/operators/intersect_spec.rb +37 -0
  123. data/spec/operators/operator_spec.rb +16 -0
  124. data/spec/operators/power_spec.rb +57 -0
  125. data/spec/operators/product_spec.rb +39 -0
  126. data/spec/operators/subtract_spec.rb +37 -0
  127. data/spec/operators/union_spec.rb +37 -0
  128. data/spec/operators/unique_spec.rb +25 -0
  129. data/spec/spec_helper.rb +2 -1
  130. data/spec/unique_filter_spec.rb +108 -18
  131. data/spec/wordlist_spec.rb +55 -3
  132. data/spec/words_spec.rb +41 -0
  133. metadata +183 -120
  134. data/lib/wordlist/builders/website.rb +0 -216
  135. data/lib/wordlist/builders.rb +0 -1
  136. data/lib/wordlist/flat_file.rb +0 -47
  137. data/lib/wordlist/list.rb +0 -162
  138. data/lib/wordlist/mutator.rb +0 -113
  139. data/lib/wordlist/parsers.rb +0 -74
  140. data/lib/wordlist/runners/list.rb +0 -116
  141. data/lib/wordlist/runners/runner.rb +0 -67
  142. data/lib/wordlist/runners.rb +0 -2
  143. data/scripts/benchmark +0 -59
  144. data/scripts/text/comedy_of_errors.txt +0 -4011
  145. data/spec/flat_file_spec.rb +0 -25
  146. data/spec/list_spec.rb +0 -58
  147. data/spec/mutator_spec.rb +0 -43
  148. data/spec/parsers_spec.rb +0 -118
@@ -0,0 +1,54 @@
1
+ require 'wordlist/operators/binary_operator'
2
+ require 'wordlist/unique_filter'
3
+
4
+ module Wordlist
5
+ module Operators
6
+ #
7
+ # Lazily enumerates over every word in the first wordlist, that is not in
8
+ # the second wordlist.
9
+ #
10
+ # @since 1.0.0
11
+ #
12
+ class Subtract < BinaryOperator
13
+
14
+ #
15
+ # Enumerates over the difference between the two wordlists.
16
+ #
17
+ # @yield [word]
18
+ # The given block will be passed each word from the first wordlist,
19
+ # that is not in the second wordlist.
20
+ #
21
+ # @yieldparam [String] word
22
+ # A word that belongs to first wordlist, but not the second wordlist.
23
+ #
24
+ # @return [Enumerator]
25
+ # If no block is given, an Enumerator object will be returned.
26
+ #
27
+ # @example
28
+ # wordlist1 = Wordlist::List["foo", "bar", baz", "qux"]
29
+ # wordlist2 = Wordlist::List["bar", "qux"]
30
+ # (wordlist1 - wordlist2).each do |word|
31
+ # puts word
32
+ # end
33
+ # # foo
34
+ # # baz
35
+ #
36
+ # @api public
37
+ #
38
+ def each
39
+ return enum_for(__method__) unless block_given?
40
+
41
+ unique_filter = UniqueFilter.new
42
+
43
+ @right.each { |word| unique_filter.add(word) }
44
+
45
+ @left.each do |word|
46
+ unless unique_filter.include?(word)
47
+ yield word
48
+ end
49
+ end
50
+ end
51
+
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,29 @@
1
+ require 'wordlist/operators/operator'
2
+
3
+ module Wordlist
4
+ module Operators
5
+ #
6
+ # Unary operator base class.
7
+ #
8
+ # @since 1.0.0
9
+ #
10
+ class UnaryOperator < Operator
11
+
12
+ # The wordlist to operate on.
13
+ #
14
+ # @return [Enumerable]
15
+ attr_reader :wordlist
16
+
17
+ #
18
+ # Initializes the unary operator.
19
+ #
20
+ # @param [Enumerable] wordlist
21
+ # The wordlist.
22
+ #
23
+ def initialize(wordlist)
24
+ @wordlist = wordlist
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,61 @@
1
+ require 'wordlist/operators/binary_operator'
2
+ require 'wordlist/unique_filter'
3
+
4
+ module Wordlist
5
+ module Operators
6
+ #
7
+ # Lazily enumerates over words from both wordlists, filtering out any
8
+ # duplicates.
9
+ #
10
+ # @since 1.0.0
11
+ #
12
+ class Union < BinaryOperator
13
+
14
+ #
15
+ # Enumerates over the union of the two wordlists.
16
+ #
17
+ # @yield [word]
18
+ # The given block will be passed each word from both wordlists,
19
+ # without duplicates.
20
+ #
21
+ # @yieldparam [String] word
22
+ # A word that belongs to one of the wordlists.
23
+ #
24
+ # @return [Enumerator]
25
+ # If no block is given, an Enumerator object will be returned.
26
+ #
27
+ # @example
28
+ # wordlist1 = Wordlist::List["foo", "bar", "baz", "qux"]
29
+ # wordlist2 = Wordlist::List["xyz", "bar", "abc", "qux"]
30
+ # (wordlist1 | wordlist2).each do |word|
31
+ # puts word
32
+ # end
33
+ # # foo
34
+ # # bar
35
+ # # baz
36
+ # # qux
37
+ # # xyz
38
+ # # abc
39
+ #
40
+ # @api public
41
+ #
42
+ def each
43
+ return enum_for(__method__) unless block_given?
44
+
45
+ unique_filter = UniqueFilter.new
46
+
47
+ @left.each do |word|
48
+ yield word
49
+ unique_filter.add(word)
50
+ end
51
+
52
+ @right.each do |word|
53
+ unless unique_filter.include?(word)
54
+ yield word
55
+ end
56
+ end
57
+ end
58
+
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,52 @@
1
+ require 'wordlist/operators/unary_operator'
2
+ require 'wordlist/unique_filter'
3
+
4
+ module Wordlist
5
+ module Operators
6
+ #
7
+ # Lazily enumerates over only the unique words in the wordlist, filtering
8
+ # out duplicates.
9
+ #
10
+ # @since 1.0.0
11
+ #
12
+ class Unique < UnaryOperator
13
+
14
+ #
15
+ # Enumerates over the unique words in the wordlist.
16
+ #
17
+ # @yield [word]
18
+ # The given block will be passed each unique word from the wordlist.
19
+ #
20
+ # @yieldparam [String] word
21
+ # A unique word from the wordlist.
22
+ #
23
+ # @return [Enumerator]
24
+ # If no block is given, an Enumerator object will be returned.
25
+ #
26
+ # @example
27
+ # wordlist= Wordlist::List["foo", "bar", "baz", "qux"]
28
+ # (wordlist + wordlist).uniq.each do |word|
29
+ # puts word
30
+ # end
31
+ # # foo
32
+ # # bar
33
+ # # baz
34
+ # # qux
35
+ #
36
+ # @api public
37
+ #
38
+ def each
39
+ return enum_for(__method__) unless block_given?
40
+
41
+ unique_filter = UniqueFilter.new
42
+
43
+ @wordlist.each do |word|
44
+ if unique_filter.add?(word)
45
+ yield word
46
+ end
47
+ end
48
+ end
49
+
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,7 @@
1
+ require 'wordlist/operators/concat'
2
+ require 'wordlist/operators/subtract'
3
+ require 'wordlist/operators/product'
4
+ require 'wordlist/operators/power'
5
+ require 'wordlist/operators/intersect'
6
+ require 'wordlist/operators/union'
7
+ require 'wordlist/operators/unique'
@@ -1,16 +1,25 @@
1
1
  require 'set'
2
2
 
3
3
  module Wordlist
4
+ #
5
+ # Acts as a filter to filter out duplicate words.
6
+ #
7
+ # @api semipublic
8
+ #
9
+ # @since 1.0.0
10
+ #
4
11
  class UniqueFilter
5
12
 
6
- # CRC32 Hashes of words seen so far
7
- attr_reader :seen
13
+ # The seen String hashes
14
+ #
15
+ # @return [Set<Integer>]
16
+ attr_reader :hashes
8
17
 
9
18
  #
10
- # Creates a new UniqueFilter object.
19
+ # Creates a new unique filter.
11
20
  #
12
21
  def initialize
13
- @seen = {}
22
+ @hashes = Set.new
14
23
  end
15
24
 
16
25
  #
@@ -22,90 +31,60 @@ module Wordlist
22
31
  # @return [Boolean]
23
32
  # Specifies whether the word has been previously seen.
24
33
  #
25
- def seen?(word)
26
- length = word.length
27
-
28
- (@seen.has_key?(length) && @seen[length].include?(crc32(word)))
34
+ def include?(word)
35
+ @hashes.include?(word.hash)
29
36
  end
30
37
 
31
38
  #
32
- # Marks the given word as previously seen.
39
+ # Adds the word to the unique filter.
33
40
  #
34
41
  # @param [String] word
35
- # The word to mark as previously seen.
42
+ # The word to add.
36
43
  #
37
- # @return [Boolean]
38
- # Specifies whether or not the word has not been previously seen
39
- # until now.
40
- #
41
- def saw!(word)
42
- length = word.length
43
- crc = crc32(word)
44
-
45
- if @seen.has_key?(length)
46
- return false if @seen[length].include?(crc)
47
- @seen[length] << crc
48
- else
49
- @seen[length] = SortedSet[crc]
50
- end
51
-
52
- return true
44
+ def add(word)
45
+ @hashes.add(word.hash)
53
46
  end
54
47
 
48
+ alias << add
49
+
55
50
  #
56
- # Passes the given word through the unique filter.
51
+ # Attempts to add the word to the unique filter.
57
52
  #
58
53
  # @param [String] word
59
- # The word to pass through the unique filter.
54
+ # The word to add.
60
55
  #
61
- # @yield [word]
62
- # The given block will be passed the word, if the word has not been
63
- # previously seen by the filter.
56
+ # @return [Boolean]
57
+ # Returns `true` if the word does not yet exist in the unique filter.
58
+ # Returns `false` if the word already exists in the unique filter.
64
59
  #
65
- # @yieldparam [String] word
66
- # A unique word that has not been previously seen by the filter.
60
+ def add?(word)
61
+ !@hashes.add?(word.hash).nil?
62
+ end
63
+
67
64
  #
68
- # @return [nil]
65
+ # Determines if the unique filter is empty or not.
69
66
  #
70
- def pass(word)
71
- if saw!(word)
72
- yield word
73
- end
74
-
75
- return nil
67
+ # @return [Boolean]
68
+ #
69
+ def empty?
70
+ @hashes.empty?
76
71
  end
77
72
 
78
73
  #
79
74
  # Clears the unique filter.
80
75
  #
81
- # @return [UniqueFilter]
82
- # The cleared filter.
83
- #
84
76
  def clear
85
- @seen.clear
86
- return self
77
+ @hashes.clear
87
78
  end
88
79
 
89
- protected
90
-
91
80
  #
92
- # Returns the CRC32 checksum of the given word.
93
- #
94
- # @param [String] word
95
- # The word to calculate a CRC32 checksum for.
81
+ # The size of the unique filter.
96
82
  #
97
83
  # @return [Integer]
98
- # The CRC32 checksum for the given word.
84
+ # The number of unique words seen by the unique filter.
99
85
  #
100
- def crc32(word)
101
- r = 0xffffffff
102
-
103
- word.each_byte do |b|
104
- r ^= b
105
- 8.times { r = ((r >> 1) ^ (0xEDB88320 * (r & 1))) }
106
- end
107
-
108
- r ^ 0xffffffff
86
+ def size
87
+ @hashes.size
109
88
  end
110
89
 
111
90
  end
@@ -1,4 +1,4 @@
1
1
  module Wordlist
2
2
  # Word version
3
- VERSION = '0.1.1'
3
+ VERSION = '1.0.0'
4
4
  end
@@ -0,0 +1,71 @@
1
+ require "wordlist/abstract_wordlist"
2
+
3
+ module Wordlist
4
+ #
5
+ # An in-memory wordlist of words.
6
+ #
7
+ # Wordlist::Words["foo", "bar", "baz"]
8
+ #
9
+ # @api public
10
+ #
11
+ # @since 1.0.0
12
+ #
13
+ class Words < AbstractWordlist
14
+
15
+ # The words in the wordlist.
16
+ #
17
+ # @return [Array<String>, Enumerable]
18
+ attr_reader :words
19
+
20
+ #
21
+ # Creates a new wordlist object.
22
+ #
23
+ # @param [Array<String>, Enumerable] words
24
+ # The words for the wordlist.
25
+ #
26
+ # @api public
27
+ #
28
+ def initialize(words=[])
29
+ @words = words
30
+ end
31
+
32
+ #
33
+ # Creates a new wordlist from the given words.
34
+ #
35
+ # @param [Array<String>] words
36
+ # The words for the wordlist.
37
+ #
38
+ # @example
39
+ # Wordlist::Words["foo", "bar", "baz"]
40
+ #
41
+ # @api public
42
+ #
43
+ def self.[](*words)
44
+ new(words)
45
+ end
46
+
47
+ #
48
+ # Enumerate through every word in the in-memory wordlist.
49
+ #
50
+ # @yield [word]
51
+ # The given block will be passed each word in the list.
52
+ #
53
+ # @yieldparam [String] word
54
+ # A word from the in-memory wordlist.
55
+ #
56
+ # @return [Enumerator]
57
+ # If no block is given, then an `Enumerator` object will be returned.
58
+ #
59
+ # @example
60
+ # words.each do |word|
61
+ # puts word
62
+ # end
63
+ #
64
+ # @api public
65
+ #
66
+ def each(&block)
67
+ @words.each(&block)
68
+ end
69
+
70
+ end
71
+ end
data/lib/wordlist.rb CHANGED
@@ -1,4 +1,105 @@
1
+ require 'wordlist/words'
2
+ require 'wordlist/file'
1
3
  require 'wordlist/builder'
2
- require 'wordlist/list'
3
- require 'wordlist/flat_file'
4
4
  require 'wordlist/version'
5
+
6
+ module Wordlist
7
+ #
8
+ # Creates an in-memory wordlist from the given words.
9
+ #
10
+ # @param [Array<String>] words
11
+ # The literal words for the list.
12
+ #
13
+ # @return [File]
14
+ # The in-memory wordlist.
15
+ #
16
+ # @api public
17
+ #
18
+ # @since 1.0.0
19
+ #
20
+ def self.[](*words)
21
+ Words[*words]
22
+ end
23
+
24
+ #
25
+ # Opens a wordlist file.
26
+ #
27
+ # @param [String] path
28
+ # The path to the file.
29
+ #
30
+ # @param [Hash{Symbol => Object}] kwargs
31
+ # Additional keyword arguments for {Wordlist::File#initialize}.
32
+ #
33
+ # @option [:txt, :bzip, :bzip2, :xz] :format
34
+ # Specifies the format of the wordlist. If no format is given, the format
35
+ # will be inferred from the path's file extension.
36
+ #
37
+ # @yield [wordlist]
38
+ # If a block is given, it will be passed the newly opened wordlist.
39
+ #
40
+ # @yieldparam [Wordlist::File] wordlist
41
+ # The newly opened wordlist.
42
+ #
43
+ # @return [Wordlist::File]
44
+ # The opened wordlist.
45
+ #
46
+ # @raise [ArgumentError]
47
+ # No `format:` was given, the wordlist format could not be inferred from the
48
+ # path's file extension.
49
+ #
50
+ # @api public
51
+ #
52
+ # @since 1.0.0
53
+ #
54
+ def self.open(path,**kwargs,&block)
55
+ File.open(path,**kwargs,&block)
56
+ end
57
+
58
+ #
59
+ # Creates a new wordlist builder.
60
+ #
61
+ # @param [String] path
62
+ # The path to the file.
63
+ #
64
+ # @param [Hash{Symbol => Object}] kwargs
65
+ # Additional keyword arguments for {Builder#initialize}.
66
+ #
67
+ # @option kwargs [:txt, :bzip, :bzip2, :xz, nil] :format
68
+ # Specifies the format of the wordlist. If no format is given, the format
69
+ # will be inferred from the path's file extension.
70
+ #
71
+ # @option kwargs [Boolean] :append
72
+ # Specifies whether new words will be appended onto the end of the wordlist
73
+ # file or if it will be overwritten.
74
+ #
75
+ # @yield [builder]
76
+ # If a block is given, the newly created builder object will be yielded.
77
+ # After the block has returned the builder will automatically be closed.
78
+ #
79
+ # @yieldparam [Builder] builder
80
+ # The newly created builder object.
81
+ #
82
+ # @return [Builder]
83
+ #
84
+ # @raise [ArgumentError]
85
+ # No `format:` was given, the wordlist format could not be inferred from the
86
+ # path's file extension.
87
+ #
88
+ # @api public
89
+ #
90
+ # @since 1.0.0
91
+ #
92
+ def self.build(path,**kwargs)
93
+ builder = Builder.new(path,**kwargs)
94
+
95
+ if block_given?
96
+ begin
97
+ yield builder
98
+ ensure
99
+ builder.close
100
+ end
101
+ end
102
+
103
+ return builder
104
+ end
105
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+ require 'wordlist/abstract_wordlist'
3
+
4
+ describe Wordlist::AbstractWordlist do
5
+ it do
6
+ expect(described_class).to include(Enumerable)
7
+ end
8
+
9
+ it do
10
+ expect(described_class).to include(Wordlist::ListMethods)
11
+ end
12
+
13
+ describe "#each" do
14
+ it do
15
+ expect { subject.each }.to raise_error(NotImplementedError,"#{described_class}#each was not implemented")
16
+ end
17
+ end
18
+ end