wordlist 0.1.1 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (152) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ruby.yml +28 -0
  3. data/.gitignore +6 -3
  4. data/ChangeLog.md +55 -1
  5. data/Gemfile +15 -0
  6. data/LICENSE.txt +1 -3
  7. data/README.md +301 -60
  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 +13 -12
  54. data/lib/wordlist/abstract_wordlist.rb +25 -0
  55. data/lib/wordlist/builder.rb +172 -138
  56. data/lib/wordlist/cli.rb +459 -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 +177 -0
  61. data/lib/wordlist/format.rb +39 -0
  62. data/lib/wordlist/lexer/lang.rb +34 -0
  63. data/lib/wordlist/lexer/stop_words.rb +69 -0
  64. data/lib/wordlist/lexer.rb +221 -0
  65. data/lib/wordlist/list_methods.rb +462 -0
  66. data/lib/wordlist/modifiers/capitalize.rb +46 -0
  67. data/lib/wordlist/modifiers/downcase.rb +46 -0
  68. data/lib/wordlist/modifiers/gsub.rb +52 -0
  69. data/lib/wordlist/modifiers/modifier.rb +44 -0
  70. data/lib/wordlist/modifiers/mutate.rb +134 -0
  71. data/lib/wordlist/modifiers/mutate_case.rb +26 -0
  72. data/lib/wordlist/modifiers/sub.rb +98 -0
  73. data/lib/wordlist/modifiers/tr.rb +72 -0
  74. data/lib/wordlist/modifiers/upcase.rb +46 -0
  75. data/lib/wordlist/modifiers.rb +9 -0
  76. data/lib/wordlist/operators/binary_operator.rb +39 -0
  77. data/lib/wordlist/operators/concat.rb +48 -0
  78. data/lib/wordlist/operators/intersect.rb +56 -0
  79. data/lib/wordlist/operators/operator.rb +29 -0
  80. data/lib/wordlist/operators/power.rb +73 -0
  81. data/lib/wordlist/operators/product.rb +51 -0
  82. data/lib/wordlist/operators/subtract.rb +55 -0
  83. data/lib/wordlist/operators/unary_operator.rb +30 -0
  84. data/lib/wordlist/operators/union.rb +62 -0
  85. data/lib/wordlist/operators/unique.rb +53 -0
  86. data/lib/wordlist/operators.rb +8 -0
  87. data/lib/wordlist/unique_filter.rb +41 -61
  88. data/lib/wordlist/version.rb +4 -2
  89. data/lib/wordlist/words.rb +72 -0
  90. data/lib/wordlist.rb +104 -2
  91. data/spec/abstract_list_spec.rb +18 -0
  92. data/spec/builder_spec.rb +220 -76
  93. data/spec/cli_spec.rb +802 -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 +269 -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 +718 -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/unary_operator_spec.rb +14 -0
  128. data/spec/operators/union_spec.rb +37 -0
  129. data/spec/operators/unique_spec.rb +25 -0
  130. data/spec/spec_helper.rb +2 -1
  131. data/spec/unique_filter_spec.rb +108 -18
  132. data/spec/wordlist_spec.rb +55 -3
  133. data/spec/words_spec.rb +41 -0
  134. data/wordlist.gemspec +1 -0
  135. metadata +164 -126
  136. data/lib/wordlist/builders/website.rb +0 -216
  137. data/lib/wordlist/builders.rb +0 -1
  138. data/lib/wordlist/flat_file.rb +0 -47
  139. data/lib/wordlist/list.rb +0 -162
  140. data/lib/wordlist/mutator.rb +0 -113
  141. data/lib/wordlist/parsers.rb +0 -74
  142. data/lib/wordlist/runners/list.rb +0 -116
  143. data/lib/wordlist/runners/runner.rb +0 -67
  144. data/lib/wordlist/runners.rb +0 -2
  145. data/scripts/benchmark +0 -59
  146. data/scripts/text/comedy_of_errors.txt +0 -4011
  147. data/spec/classes/parser_class.rb +0 -7
  148. data/spec/classes/test_list.rb +0 -9
  149. data/spec/flat_file_spec.rb +0 -25
  150. data/spec/list_spec.rb +0 -58
  151. data/spec/mutator_spec.rb +0 -43
  152. data/spec/parsers_spec.rb +0 -118
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+ require 'wordlist/operators/binary_operator'
3
+ require 'wordlist/unique_filter'
4
+
5
+ module Wordlist
6
+ module Operators
7
+ #
8
+ # Lazily enumerates over every word in the first wordlist, that is not in
9
+ # the second wordlist.
10
+ #
11
+ # @since 1.0.0
12
+ #
13
+ class Subtract < BinaryOperator
14
+
15
+ #
16
+ # Enumerates over the difference between the two wordlists.
17
+ #
18
+ # @yield [word]
19
+ # The given block will be passed each word from the first wordlist,
20
+ # that is not in the second wordlist.
21
+ #
22
+ # @yieldparam [String] word
23
+ # A word that belongs to first wordlist, but not the second wordlist.
24
+ #
25
+ # @return [Enumerator]
26
+ # If no block is given, an Enumerator object will be returned.
27
+ #
28
+ # @example
29
+ # wordlist1 = Wordlist::Words["foo", "bar", baz", "qux"]
30
+ # wordlist2 = Wordlist::Words["bar", "qux"]
31
+ # (wordlist1 - wordlist2).each do |word|
32
+ # puts word
33
+ # end
34
+ # # foo
35
+ # # baz
36
+ #
37
+ # @api public
38
+ #
39
+ def each
40
+ return enum_for(__method__) unless block_given?
41
+
42
+ unique_filter = UniqueFilter.new
43
+
44
+ @right.each { |word| unique_filter.add(word) }
45
+
46
+ @left.each do |word|
47
+ unless unique_filter.include?(word)
48
+ yield word
49
+ end
50
+ end
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ require 'wordlist/operators/operator'
3
+
4
+ module Wordlist
5
+ module Operators
6
+ #
7
+ # Unary operator base class.
8
+ #
9
+ # @since 1.0.0
10
+ #
11
+ class UnaryOperator < Operator
12
+
13
+ # The wordlist to operate on.
14
+ #
15
+ # @return [Enumerable]
16
+ attr_reader :wordlist
17
+
18
+ #
19
+ # Initializes the unary operator.
20
+ #
21
+ # @param [Enumerable] wordlist
22
+ # The wordlist.
23
+ #
24
+ def initialize(wordlist)
25
+ @wordlist = wordlist
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+ require 'wordlist/operators/binary_operator'
3
+ require 'wordlist/unique_filter'
4
+
5
+ module Wordlist
6
+ module Operators
7
+ #
8
+ # Lazily enumerates over words from both wordlists, filtering out any
9
+ # duplicates.
10
+ #
11
+ # @since 1.0.0
12
+ #
13
+ class Union < BinaryOperator
14
+
15
+ #
16
+ # Enumerates over the union of the two wordlists.
17
+ #
18
+ # @yield [word]
19
+ # The given block will be passed each word from both wordlists,
20
+ # without duplicates.
21
+ #
22
+ # @yieldparam [String] word
23
+ # A word that belongs to one of the wordlists.
24
+ #
25
+ # @return [Enumerator]
26
+ # If no block is given, an Enumerator object will be returned.
27
+ #
28
+ # @example
29
+ # wordlist1 = Wordlist::Words["foo", "bar", "baz", "qux"]
30
+ # wordlist2 = Wordlist::Words["xyz", "bar", "abc", "qux"]
31
+ # (wordlist1 | wordlist2).each do |word|
32
+ # puts word
33
+ # end
34
+ # # foo
35
+ # # bar
36
+ # # baz
37
+ # # qux
38
+ # # xyz
39
+ # # abc
40
+ #
41
+ # @api public
42
+ #
43
+ def each
44
+ return enum_for(__method__) unless block_given?
45
+
46
+ unique_filter = UniqueFilter.new
47
+
48
+ @left.each do |word|
49
+ yield word
50
+ unique_filter.add(word)
51
+ end
52
+
53
+ @right.each do |word|
54
+ unless unique_filter.include?(word)
55
+ yield word
56
+ end
57
+ end
58
+ end
59
+
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+ require 'wordlist/operators/unary_operator'
3
+ require 'wordlist/unique_filter'
4
+
5
+ module Wordlist
6
+ module Operators
7
+ #
8
+ # Lazily enumerates over only the unique words in the wordlist, filtering
9
+ # out duplicates.
10
+ #
11
+ # @since 1.0.0
12
+ #
13
+ class Unique < UnaryOperator
14
+
15
+ #
16
+ # Enumerates over the unique words in the wordlist.
17
+ #
18
+ # @yield [word]
19
+ # The given block will be passed each unique word from the wordlist.
20
+ #
21
+ # @yieldparam [String] word
22
+ # A unique word from the wordlist.
23
+ #
24
+ # @return [Enumerator]
25
+ # If no block is given, an Enumerator object will be returned.
26
+ #
27
+ # @example
28
+ # wordlist= Wordlist::Words["foo", "bar", "baz", "qux"]
29
+ # (wordlist + wordlist).uniq.each do |word|
30
+ # puts word
31
+ # end
32
+ # # foo
33
+ # # bar
34
+ # # baz
35
+ # # qux
36
+ #
37
+ # @api public
38
+ #
39
+ def each
40
+ return enum_for(__method__) unless block_given?
41
+
42
+ unique_filter = UniqueFilter.new
43
+
44
+ @wordlist.each do |word|
45
+ if unique_filter.add?(word)
46
+ yield word
47
+ end
48
+ end
49
+ end
50
+
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+ require 'wordlist/operators/concat'
3
+ require 'wordlist/operators/subtract'
4
+ require 'wordlist/operators/product'
5
+ require 'wordlist/operators/power'
6
+ require 'wordlist/operators/intersect'
7
+ require 'wordlist/operators/union'
8
+ require 'wordlist/operators/unique'
@@ -1,16 +1,26 @@
1
+ # frozen_string_literal: true
1
2
  require 'set'
2
3
 
3
4
  module Wordlist
5
+ #
6
+ # Acts as a filter to filter out duplicate words.
7
+ #
8
+ # @api semipublic
9
+ #
10
+ # @since 1.0.0
11
+ #
4
12
  class UniqueFilter
5
13
 
6
- # CRC32 Hashes of words seen so far
7
- attr_reader :seen
14
+ # The seen String hashes
15
+ #
16
+ # @return [Set<Integer>]
17
+ attr_reader :hashes
8
18
 
9
19
  #
10
- # Creates a new UniqueFilter object.
20
+ # Creates a new unique filter.
11
21
  #
12
22
  def initialize
13
- @seen = {}
23
+ @hashes = Set.new
14
24
  end
15
25
 
16
26
  #
@@ -22,90 +32,60 @@ module Wordlist
22
32
  # @return [Boolean]
23
33
  # Specifies whether the word has been previously seen.
24
34
  #
25
- def seen?(word)
26
- length = word.length
27
-
28
- (@seen.has_key?(length) && @seen[length].include?(crc32(word)))
35
+ def include?(word)
36
+ @hashes.include?(word.hash)
29
37
  end
30
38
 
31
39
  #
32
- # Marks the given word as previously seen.
40
+ # Adds the word to the unique filter.
33
41
  #
34
42
  # @param [String] word
35
- # The word to mark as previously seen.
43
+ # The word to add.
36
44
  #
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
45
+ def add(word)
46
+ @hashes.add(word.hash)
53
47
  end
54
48
 
49
+ alias << add
50
+
55
51
  #
56
- # Passes the given word through the unique filter.
52
+ # Attempts to add the word to the unique filter.
57
53
  #
58
54
  # @param [String] word
59
- # The word to pass through the unique filter.
55
+ # The word to add.
60
56
  #
61
- # @yield [word]
62
- # The given block will be passed the word, if the word has not been
63
- # previously seen by the filter.
57
+ # @return [Boolean]
58
+ # Returns `true` if the word does not yet exist in the unique filter.
59
+ # Returns `false` if the word already exists in the unique filter.
64
60
  #
65
- # @yieldparam [String] word
66
- # A unique word that has not been previously seen by the filter.
61
+ def add?(word)
62
+ !@hashes.add?(word.hash).nil?
63
+ end
64
+
67
65
  #
68
- # @return [nil]
66
+ # Determines if the unique filter is empty or not.
69
67
  #
70
- def pass(word)
71
- if saw!(word)
72
- yield word
73
- end
74
-
75
- return nil
68
+ # @return [Boolean]
69
+ #
70
+ def empty?
71
+ @hashes.empty?
76
72
  end
77
73
 
78
74
  #
79
75
  # Clears the unique filter.
80
76
  #
81
- # @return [UniqueFilter]
82
- # The cleared filter.
83
- #
84
77
  def clear
85
- @seen.clear
86
- return self
78
+ @hashes.clear
87
79
  end
88
80
 
89
- protected
90
-
91
81
  #
92
- # Returns the CRC32 checksum of the given word.
93
- #
94
- # @param [String] word
95
- # The word to calculate a CRC32 checksum for.
82
+ # The size of the unique filter.
96
83
  #
97
84
  # @return [Integer]
98
- # The CRC32 checksum for the given word.
85
+ # The number of unique words seen by the unique filter.
99
86
  #
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
87
+ def size
88
+ @hashes.size
109
89
  end
110
90
 
111
91
  end
@@ -1,4 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Wordlist
2
- # Word version
3
- VERSION = '0.1.1'
4
+ # wordlist version
5
+ VERSION = '1.0.1'
4
6
  end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+ require "wordlist/abstract_wordlist"
3
+
4
+ module Wordlist
5
+ #
6
+ # An in-memory wordlist of words.
7
+ #
8
+ # Wordlist::Words["foo", "bar", "baz"]
9
+ #
10
+ # @api public
11
+ #
12
+ # @since 1.0.0
13
+ #
14
+ class Words < AbstractWordlist
15
+
16
+ # The words in the wordlist.
17
+ #
18
+ # @return [Array<String>, Enumerable]
19
+ attr_reader :words
20
+
21
+ #
22
+ # Creates a new wordlist object.
23
+ #
24
+ # @param [Array<String>, Enumerable] words
25
+ # The words for the wordlist.
26
+ #
27
+ # @api public
28
+ #
29
+ def initialize(words=[])
30
+ @words = words
31
+ end
32
+
33
+ #
34
+ # Creates a new wordlist from the given words.
35
+ #
36
+ # @param [Array<String>] words
37
+ # The words for the wordlist.
38
+ #
39
+ # @example
40
+ # Wordlist::Words["foo", "bar", "baz"]
41
+ #
42
+ # @api public
43
+ #
44
+ def self.[](*words)
45
+ new(words)
46
+ end
47
+
48
+ #
49
+ # Enumerate through every word in the in-memory wordlist.
50
+ #
51
+ # @yield [word]
52
+ # The given block will be passed each word in the list.
53
+ #
54
+ # @yieldparam [String] word
55
+ # A word from the in-memory wordlist.
56
+ #
57
+ # @return [Enumerator]
58
+ # If no block is given, then an `Enumerator` object will be returned.
59
+ #
60
+ # @example
61
+ # words.each do |word|
62
+ # puts word
63
+ # end
64
+ #
65
+ # @api public
66
+ #
67
+ def each(&block)
68
+ @words.each(&block)
69
+ end
70
+
71
+ end
72
+ end
data/lib/wordlist.rb CHANGED
@@ -1,4 +1,106 @@
1
+ # frozen_string_literal: true
2
+ require 'wordlist/words'
3
+ require 'wordlist/file'
1
4
  require 'wordlist/builder'
2
- require 'wordlist/list'
3
- require 'wordlist/flat_file'
4
5
  require 'wordlist/version'
6
+
7
+ module Wordlist
8
+ #
9
+ # Creates an in-memory wordlist from the given words.
10
+ #
11
+ # @param [Array<String>] words
12
+ # The literal words for the list.
13
+ #
14
+ # @return [File]
15
+ # The in-memory wordlist.
16
+ #
17
+ # @api public
18
+ #
19
+ # @since 1.0.0
20
+ #
21
+ def self.[](*words)
22
+ Words[*words]
23
+ end
24
+
25
+ #
26
+ # Opens a wordlist file.
27
+ #
28
+ # @param [String] path
29
+ # The path to the file.
30
+ #
31
+ # @param [Hash{Symbol => Object}] kwargs
32
+ # Additional keyword arguments for {Wordlist::File#initialize}.
33
+ #
34
+ # @option kwargs [:txt, :bzip, :bzip2, :xz] :format
35
+ # Specifies the format of the wordlist. If no format is given, the format
36
+ # will be inferred from the path's file extension.
37
+ #
38
+ # @yield [wordlist]
39
+ # If a block is given, it will be passed the newly opened wordlist.
40
+ #
41
+ # @yieldparam [Wordlist::File] wordlist
42
+ # The newly opened wordlist.
43
+ #
44
+ # @return [Wordlist::File]
45
+ # The opened wordlist.
46
+ #
47
+ # @raise [ArgumentError]
48
+ # No `format:` was given, the wordlist format could not be inferred from the
49
+ # path's file extension.
50
+ #
51
+ # @api public
52
+ #
53
+ # @since 1.0.0
54
+ #
55
+ def self.open(path,**kwargs,&block)
56
+ File.open(path,**kwargs,&block)
57
+ end
58
+
59
+ #
60
+ # Creates a new wordlist builder.
61
+ #
62
+ # @param [String] path
63
+ # The path to the file.
64
+ #
65
+ # @param [Hash{Symbol => Object}] kwargs
66
+ # Additional keyword arguments for {Builder#initialize}.
67
+ #
68
+ # @option kwargs [:txt, :bzip, :bzip2, :xz, nil] :format
69
+ # Specifies the format of the wordlist. If no format is given, the format
70
+ # will be inferred from the path's file extension.
71
+ #
72
+ # @option kwargs [Boolean] :append
73
+ # Specifies whether new words will be appended onto the end of the wordlist
74
+ # file or if it will be overwritten.
75
+ #
76
+ # @yield [builder]
77
+ # If a block is given, the newly created builder object will be yielded.
78
+ # After the block has returned the builder will automatically be closed.
79
+ #
80
+ # @yieldparam [Builder] builder
81
+ # The newly created builder object.
82
+ #
83
+ # @return [Builder]
84
+ #
85
+ # @raise [ArgumentError]
86
+ # No `format:` was given, the wordlist format could not be inferred from the
87
+ # path's file extension.
88
+ #
89
+ # @api public
90
+ #
91
+ # @since 1.0.0
92
+ #
93
+ def self.build(path,**kwargs)
94
+ builder = Builder.new(path,**kwargs)
95
+
96
+ if block_given?
97
+ begin
98
+ yield builder
99
+ ensure
100
+ builder.close
101
+ end
102
+ end
103
+
104
+ return builder
105
+ end
106
+ 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