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
data/spec/cli_spec.rb ADDED
@@ -0,0 +1,802 @@
1
+ require 'spec_helper'
2
+ require 'wordlist/cli'
3
+
4
+ describe Wordlist::CLI do
5
+ describe "#initialize" do
6
+ it "must default #mode to :read" do
7
+ expect(subject.mode).to eq(:read)
8
+ end
9
+
10
+ it "must default #format to nil" do
11
+ expect(subject.format).to be(nil)
12
+ end
13
+
14
+ it "must default #command to nil" do
15
+ expect(subject.command).to be(nil)
16
+ end
17
+
18
+ it "must default #output to nil" do
19
+ expect(subject.output).to be(nil)
20
+ end
21
+
22
+ it "must initialize #option_parser" do
23
+ expect(subject.option_parser).to be_kind_of(OptionParser)
24
+ end
25
+
26
+ it "must initialize #operators to []" do
27
+ expect(subject.operators).to eq([])
28
+ end
29
+
30
+ it "must initialize #modifiers to []" do
31
+ expect(subject.modifiers).to eq([])
32
+ end
33
+
34
+ it "must initialize #build_options to {}" do
35
+ expect(subject.builder_options).to eq({})
36
+ end
37
+ end
38
+
39
+ describe "#print_error" do
40
+ let(:error) { "error!" }
41
+
42
+ it "must print the program name and the error message to stderr" do
43
+ expect {
44
+ subject.print_error(error)
45
+ }.to output("#{described_class::PROGRAM_NAME}: #{error}#{$/}").to_stderr
46
+ end
47
+ end
48
+
49
+ describe "#print_backtrace" do
50
+ let(:exception) { RuntimeError.new("error!") }
51
+
52
+ it "must print the program name and the error message to stderr" do
53
+ expect {
54
+ subject.print_backtrace(exception)
55
+ }.to output(
56
+ %r{Oops! Looks like you've found a bug!
57
+ Please report the following text to: #{Regexp.escape(described_class::BUG_REPORT_URL)}
58
+
59
+ ```}m
60
+ ).to_stderr
61
+ end
62
+ end
63
+
64
+ let(:fixtures_dir) { File.join(__dir__,'fixtures') }
65
+
66
+ describe "#open_wordlist" do
67
+ context "when #format is set" do
68
+ let(:format) { :gzip }
69
+ let(:path) { ::File.join(fixtures_dir,'wordlist_with_ambiguous_format') }
70
+
71
+ subject { described_class.new(format: format) }
72
+
73
+ it "must call Wordlist::File.new with a format: keyword argument" do
74
+ wordlist = subject.open_wordlist(path)
75
+
76
+ expect(wordlist.format).to be(format)
77
+ end
78
+ end
79
+
80
+ context "when #format is not set" do
81
+ let(:path) { ::File.join(fixtures_dir,'wordlist.txt.gz') }
82
+
83
+ it "must let Wordlist::File.new infer the format" do
84
+ wordlist = subject.open_wordlist(path)
85
+
86
+ expect(wordlist.format).to be(:gzip)
87
+ end
88
+
89
+ context "and the file's format cannot be inferred" do
90
+ let(:path) { ::File.join(fixtures_dir,'wordlist_with_ambiguous_format') }
91
+
92
+ it "must print an error and exit with -1" do
93
+ expect(subject).to receive(:exit).with(-1)
94
+
95
+ expect {
96
+ subject.open_wordlist(path)
97
+ }.to output("#{described_class::PROGRAM_NAME}: could not infer the format of file: #{path.inspect}#{$/}").to_stderr
98
+ end
99
+ end
100
+ end
101
+
102
+ context "when the file does not exist" do
103
+ let(:path) { 'does/not/exist.txt' }
104
+ let(:absolute_path) { File.expand_path(path) }
105
+
106
+ it "must print an error and exit with -1" do
107
+ expect(subject).to receive(:exit).with(-1)
108
+
109
+ expect {
110
+ subject.open_wordlist(path)
111
+ }.to output("#{described_class::PROGRAM_NAME}: wordlist file does not exist: #{absolute_path.inspect}#{$/}").to_stderr
112
+ end
113
+ end
114
+ end
115
+
116
+ describe "#add_operator" do
117
+ let(:op1) { :+ }
118
+ let(:wordlist1) { double(:other_wordlist1) }
119
+
120
+ let(:op2) { :* }
121
+ let(:wordlist2) { double(:other_wordlist2) }
122
+
123
+ before do
124
+ subject.add_operator(op1, wordlist1)
125
+ subject.add_operator(op2, wordlist2)
126
+ end
127
+
128
+ it "must append an operator and it's arguments to #operators" do
129
+ expect(subject.operators[0]).to be_a(Array)
130
+ expect(subject.operators[0][0]).to eq(op1)
131
+ expect(subject.operators[0][1]).to eq([wordlist1])
132
+
133
+ expect(subject.operators[1]).to be_a(Array)
134
+ expect(subject.operators[1][0]).to eq(op2)
135
+ expect(subject.operators[1][1]).to eq([wordlist2])
136
+ end
137
+ end
138
+
139
+ describe "#add_modifier" do
140
+ let(:mod1) { :capitalize }
141
+ let(:args1) { [] }
142
+
143
+ let(:mod2) { :gsub }
144
+ let(:args2) { ['e','3'] }
145
+
146
+ before do
147
+ subject.add_modifier(mod1, *args1)
148
+ subject.add_modifier(mod2, *args2)
149
+ end
150
+
151
+ it "must append an modifier and it's arguments to #modifiers" do
152
+ expect(subject.modifiers[0]).to be_a(Array)
153
+ expect(subject.modifiers[0][0]).to eq(mod1)
154
+ expect(subject.modifiers[0][1]).to eq(args1)
155
+
156
+ expect(subject.modifiers[1]).to be_a(Array)
157
+ expect(subject.modifiers[1][0]).to eq(mod2)
158
+ expect(subject.modifiers[1][1]).to eq(args2)
159
+ end
160
+ end
161
+
162
+ describe "#option_parser" do
163
+ it do
164
+ expect(subject.option_parser).to be_kind_of(OptionParser)
165
+ end
166
+
167
+ describe "#parse" do
168
+ context "when given -f FORMAT" do
169
+ let(:format) { :gzip }
170
+ let(:argv) { ['-f', format.to_s] }
171
+
172
+ before { subject.option_parser.parse(argv) }
173
+
174
+ it "must set #format" do
175
+ expect(subject.format).to eq(format)
176
+ end
177
+ end
178
+
179
+ context "when given --format FORMAT" do
180
+ let(:format) { :gzip }
181
+ let(:argv) { ['--format', format.to_s] }
182
+
183
+ before { subject.option_parser.parse(argv) }
184
+
185
+ it "must set #format" do
186
+ expect(subject.format).to eq(format)
187
+ end
188
+ end
189
+
190
+ context "when given --exec COMMAND" do
191
+ let(:command) { "foo {}" }
192
+ let(:argv) { ['--exec', command] }
193
+
194
+ before { subject.option_parser.parse(argv) }
195
+
196
+ it "must set #command" do
197
+ expect(subject.command).to eq(command)
198
+ end
199
+ end
200
+
201
+ %w[-U --union].each do |flag|
202
+ context "when given #{flag} WORDLIST" do
203
+ let(:wordlist) { File.join(fixtures_dir,'wordlist.txt.gz') }
204
+ let(:argv) { [flag, wordlist] }
205
+
206
+ before { subject.option_parser.parse(argv) }
207
+
208
+ it "must append to #operators" do
209
+ expect(subject.operators.length).to be(1)
210
+ expect(subject.operators[0][0]).to be(:|)
211
+ expect(subject.operators[0][1].length).to be(1)
212
+ expect(subject.operators[0][1][0]).to be_kind_of(Wordlist::File)
213
+ expect(subject.operators[0][1][0].path).to eq(wordlist)
214
+ end
215
+ end
216
+ end
217
+
218
+ %w[-I --intersect].each do |flag|
219
+ context "when given #{flag} WORDLIST" do
220
+ let(:wordlist) { File.join(fixtures_dir,'wordlist.txt.gz') }
221
+ let(:argv) { [flag, wordlist] }
222
+
223
+ before { subject.option_parser.parse(argv) }
224
+
225
+ it "must append to #operators" do
226
+ expect(subject.operators.length).to be(1)
227
+ expect(subject.operators[0][0]).to be(:&)
228
+ expect(subject.operators[0][1].length).to be(1)
229
+ expect(subject.operators[0][1][0]).to be_kind_of(Wordlist::File)
230
+ expect(subject.operators[0][1][0].path).to eq(wordlist)
231
+ end
232
+ end
233
+ end
234
+
235
+ %w[-S --subtract].each do |flag|
236
+ context "when given #{flag} WORDLIST" do
237
+ let(:wordlist) { File.join(fixtures_dir,'wordlist.txt.gz') }
238
+ let(:argv) { [flag, wordlist] }
239
+
240
+ before { subject.option_parser.parse(argv) }
241
+
242
+ it "must append to #operators" do
243
+ expect(subject.operators.length).to be(1)
244
+ expect(subject.operators[0][0]).to be(:-)
245
+ expect(subject.operators[0][1].length).to be(1)
246
+ expect(subject.operators[0][1][0]).to be_kind_of(Wordlist::File)
247
+ expect(subject.operators[0][1][0].path).to eq(wordlist)
248
+ end
249
+ end
250
+ end
251
+
252
+ %w[-p --product].each do |flag|
253
+ context "when given #{flag} WORDLIST" do
254
+ let(:wordlist) { File.join(fixtures_dir,'wordlist.txt.gz') }
255
+ let(:argv) { [flag, wordlist] }
256
+
257
+ before { subject.option_parser.parse(argv) }
258
+
259
+ it "must append to #operators" do
260
+ expect(subject.operators.length).to be(1)
261
+ expect(subject.operators[0][0]).to be(:*)
262
+ expect(subject.operators[0][1].length).to be(1)
263
+ expect(subject.operators[0][1][0]).to be_kind_of(Wordlist::File)
264
+ expect(subject.operators[0][1][0].path).to eq(wordlist)
265
+ end
266
+ end
267
+ end
268
+
269
+ %w[-P --power].each do |flag|
270
+ context "when given #{flag} POWER" do
271
+ let(:power) { 3 }
272
+ let(:argv) { [flag, power.to_s] }
273
+
274
+ before { subject.option_parser.parse(argv) }
275
+
276
+ it "must append to #operators" do
277
+ expect(subject.operators.length).to be(1)
278
+ expect(subject.operators[0][0]).to be(:**)
279
+ expect(subject.operators[0][1].length).to be(1)
280
+ expect(subject.operators[0][1][0]).to be(power)
281
+ end
282
+ end
283
+ end
284
+
285
+ %w[-u --unique].each do |flag|
286
+ context "when given #{flag}" do
287
+ let(:argv) { [flag] }
288
+
289
+ before { subject.option_parser.parse(argv) }
290
+
291
+ it "must append to #operators" do
292
+ expect(subject.operators.length).to be(1)
293
+ expect(subject.operators[0][0]).to be(:uniq)
294
+ expect(subject.operators[0][1].length).to be(0)
295
+ end
296
+ end
297
+ end
298
+
299
+ %w[-C --capitalize].each do |flag|
300
+ context "when given #{flag}" do
301
+ let(:argv) { [flag] }
302
+
303
+ before { subject.option_parser.parse(argv) }
304
+
305
+ it "must append to #modifiers" do
306
+ expect(subject.modifiers.length).to be(1)
307
+ expect(subject.modifiers[0][0]).to be(:capitalize)
308
+ expect(subject.modifiers[0][1].length).to be(0)
309
+ end
310
+ end
311
+ end
312
+
313
+ %w[--uppercase --upcase].each do |flag|
314
+ context "when given #{flag} WORDLIST" do
315
+ let(:wordlist) { File.join(fixtures_dir,'wordlist.txt.gz') }
316
+ let(:argv) { [flag, wordlist] }
317
+
318
+ before { subject.option_parser.parse(argv) }
319
+
320
+ it "must append to #modifiers" do
321
+ expect(subject.modifiers.length).to be(1)
322
+ expect(subject.modifiers[0][0]).to be(:upcase)
323
+ expect(subject.modifiers[0][1].length).to be(0)
324
+ end
325
+ end
326
+ end
327
+
328
+ %w[--lowercase --downcase].each do |flag|
329
+ context "when given #{flag} WORDLIST" do
330
+ let(:wordlist) { File.join(fixtures_dir,'wordlist.txt.gz') }
331
+ let(:argv) { [flag, wordlist] }
332
+
333
+ before { subject.option_parser.parse(argv) }
334
+
335
+ it "must append to #modifiers" do
336
+ expect(subject.modifiers.length).to be(1)
337
+ expect(subject.modifiers[0][0]).to be(:downcase)
338
+ expect(subject.modifiers[0][1].length).to be(0)
339
+ end
340
+ end
341
+ end
342
+
343
+ %w[-t --tr].each do |flag|
344
+ context "when given #{flag} CHARS:REPLACE" do
345
+ let(:chars) { 'e' }
346
+ let(:replace) { '3' }
347
+ let(:argv) { [flag, "#{chars}:#{replace}"] }
348
+
349
+ before { subject.option_parser.parse(argv) }
350
+
351
+ it "must append to #modifiers" do
352
+ expect(subject.modifiers.length).to be(1)
353
+ expect(subject.modifiers[0][0]).to be(:tr)
354
+ expect(subject.modifiers[0][1].length).to be(2)
355
+ expect(subject.modifiers[0][1][0]).to eq(chars)
356
+ expect(subject.modifiers[0][1][1]).to eq(replace)
357
+ end
358
+ end
359
+ end
360
+
361
+ %w[-s --sub].each do |flag|
362
+ context "when given #{flag} CHARS:REPLACE" do
363
+ let(:chars) { 'e' }
364
+ let(:replace) { '3' }
365
+ let(:argv) { [flag, "#{chars}:#{replace}"] }
366
+
367
+ before { subject.option_parser.parse(argv) }
368
+
369
+ it "must append to #modifiers" do
370
+ expect(subject.modifiers.length).to be(1)
371
+ expect(subject.modifiers[0][0]).to be(:sub)
372
+ expect(subject.modifiers[0][1].length).to be(2)
373
+ expect(subject.modifiers[0][1][0]).to eq(chars)
374
+ expect(subject.modifiers[0][1][1]).to eq(replace)
375
+ end
376
+ end
377
+ end
378
+
379
+ %w[-g --gsub].each do |flag|
380
+ context "when given #{flag} CHARS:REPLACE" do
381
+ let(:chars) { 'e' }
382
+ let(:replace) { '3' }
383
+ let(:argv) { [flag, "#{chars}:#{replace}"] }
384
+
385
+ before { subject.option_parser.parse(argv) }
386
+
387
+ it "must append to #modifiers" do
388
+ expect(subject.modifiers.length).to be(1)
389
+ expect(subject.modifiers[0][0]).to be(:gsub)
390
+ expect(subject.modifiers[0][1].length).to be(2)
391
+ expect(subject.modifiers[0][1][0]).to eq(chars)
392
+ expect(subject.modifiers[0][1][1]).to eq(replace)
393
+ end
394
+ end
395
+ end
396
+
397
+ %w[-m --mutate].each do |flag|
398
+ context "when given #{flag} CHARS:REPLACE" do
399
+ let(:chars) { 'e' }
400
+ let(:replace) { '3' }
401
+ let(:argv) { [flag, "#{chars}:#{replace}"] }
402
+
403
+ before { subject.option_parser.parse(argv) }
404
+
405
+ it "must append to #modifiers" do
406
+ expect(subject.modifiers.length).to be(1)
407
+ expect(subject.modifiers[0][0]).to be(:mutate)
408
+ expect(subject.modifiers[0][1].length).to be(2)
409
+ expect(subject.modifiers[0][1][0]).to eq(chars)
410
+ expect(subject.modifiers[0][1][1]).to eq(replace)
411
+ end
412
+ end
413
+ end
414
+
415
+ %w[-M --mutate-case].each do |flag|
416
+ context "when given #{flag}" do
417
+ let(:argv) { [flag] }
418
+
419
+ before { subject.option_parser.parse(argv) }
420
+
421
+ it "must append to #modifiers" do
422
+ expect(subject.modifiers.length).to be(1)
423
+ expect(subject.modifiers[0][0]).to be(:mutate_case)
424
+ expect(subject.modifiers[0][1].length).to be(0)
425
+ end
426
+ end
427
+ end
428
+
429
+ %w[-b --build].each do |flag|
430
+ context "when given #{flag} WORDLIST" do
431
+ let(:wordlist) { File.join(fixtures_dir,'new_wordlist.txt') }
432
+ let(:argv) { [flag, wordlist] }
433
+
434
+ before { subject.option_parser.parse(argv) }
435
+
436
+ it "must append to #modifiers" do
437
+ expect(subject.mode).to eq(:build)
438
+ expect(subject.output).to eq(wordlist)
439
+ end
440
+ end
441
+ end
442
+
443
+ %w[-a --append].each do |flag|
444
+ context "when given #{flag}" do
445
+ let(:argv) { [flag] }
446
+
447
+ before { subject.option_parser.parse(argv) }
448
+
449
+ it "must set #builder_options[:append] to true" do
450
+ expect(subject.builder_options[:append]).to be(true)
451
+ end
452
+
453
+ context "and when given --no-append" do
454
+ let(:argv) { ['--append', '--no-append'] }
455
+
456
+ it "must set #builder_options[:append] to false" do
457
+ expect(subject.builder_options[:append]).to be(false)
458
+ end
459
+ end
460
+ end
461
+ end
462
+
463
+ %w[-L --lang].each do |flag|
464
+ context "when given #{flag} LANG" do
465
+ let(:lang) { 'fr' }
466
+ let(:argv) { [flag, lang] }
467
+
468
+ before { subject.option_parser.parse(argv) }
469
+
470
+ it "must set #builder_options[:lang] to LANG" do
471
+ expect(subject.builder_options[:lang]).to eq(lang)
472
+ end
473
+ end
474
+ end
475
+
476
+ context "when given --stop-words \"WORDS...\"" do
477
+ let(:words) { "foo bar baz" }
478
+ let(:argv) { ['--stop-words', words] }
479
+
480
+ before { subject.option_parser.parse(argv) }
481
+
482
+ it "must set #builder_options[:stop_words] to the Array of WORDS" do
483
+ expect(subject.builder_options[:stop_words]).to eq(words.split)
484
+ end
485
+ end
486
+
487
+ context "when given --ignore-words \"WORDS...\"" do
488
+ let(:words) { "foo bar baz" }
489
+ let(:argv) { ['--ignore-words', words] }
490
+
491
+ before { subject.option_parser.parse(argv) }
492
+
493
+ it "must set #builder_options[:ignore_words] to the Array of WORDS" do
494
+ expect(subject.builder_options[:ignore_words]).to eq(words.split)
495
+ end
496
+ end
497
+
498
+ context "when given --digits" do
499
+ let(:argv) { ['--digits'] }
500
+
501
+ before { subject.option_parser.parse(argv) }
502
+
503
+ it "must set #builder_options[:digits] to true" do
504
+ expect(subject.builder_options[:digits]).to be(true)
505
+ end
506
+
507
+ context "and when given --no-digits" do
508
+ let(:argv) { ['--digits', '--no-digits'] }
509
+
510
+ it "must set #builder_options[:digits] to false" do
511
+ expect(subject.builder_options[:digits]).to be(false)
512
+ end
513
+ end
514
+ end
515
+
516
+ context "when given --special-chars \"CHARS...\"" do
517
+ let(:chars) { "!@#$%^&*()_-" }
518
+ let(:argv) { ['--special-chars', chars] }
519
+
520
+ before { subject.option_parser.parse(argv) }
521
+
522
+ it "must set #builder_options[:special_chars] to the Array of CHARS" do
523
+ expect(subject.builder_options[:special_chars]).to eq(chars.chars)
524
+ end
525
+ end
526
+
527
+ context "when given --numbers" do
528
+ let(:argv) { ['--numbers'] }
529
+
530
+ before { subject.option_parser.parse(argv) }
531
+
532
+ it "must set #builder_options[:numbers] to true" do
533
+ expect(subject.builder_options[:numbers]).to be(true)
534
+ end
535
+
536
+ context "and when given --no-numbers" do
537
+ let(:argv) { ['--numbers', '--no-numbers'] }
538
+
539
+ it "must set #builder_options[:numbers] to false" do
540
+ expect(subject.builder_options[:numbers]).to be(false)
541
+ end
542
+ end
543
+ end
544
+
545
+ context "when given --acronyms" do
546
+ let(:argv) { ['--acronyms'] }
547
+
548
+ before { subject.option_parser.parse(argv) }
549
+
550
+ it "must set #builder_options[:acronyms] to true" do
551
+ expect(subject.builder_options[:acronyms]).to be(true)
552
+ end
553
+
554
+ context "and when given --no-acronyms" do
555
+ let(:argv) { ['--acronyms', '--no-acronyms'] }
556
+
557
+ it "must set #builder_options[:acronyms] to false" do
558
+ expect(subject.builder_options[:acronyms]).to be(false)
559
+ end
560
+ end
561
+ end
562
+
563
+ context "when given --normalize-case" do
564
+ let(:argv) { ['--normalize-case'] }
565
+
566
+ before { subject.option_parser.parse(argv) }
567
+
568
+ it "must set #builder_options[:normalize_case] to true" do
569
+ expect(subject.builder_options[:normalize_case]).to be(true)
570
+ end
571
+
572
+ context "and when given --no-normalize-case" do
573
+ let(:argv) { ['--normalize-case', '--no-normalize-case'] }
574
+
575
+ it "must set #builder_options[:normalize_case] to false" do
576
+ expect(subject.builder_options[:normalize_case]).to be(false)
577
+ end
578
+ end
579
+ end
580
+
581
+ context "when given --normalize-apostrophes" do
582
+ let(:argv) { ['--normalize-apostrophes'] }
583
+
584
+ before { subject.option_parser.parse(argv) }
585
+
586
+ it "must set #builder_options[:normalize_apostrophes] to true" do
587
+ expect(subject.builder_options[:normalize_apostrophes]).to be(true)
588
+ end
589
+
590
+ context "and when given --no-normalize-apostrophes" do
591
+ let(:argv) { ['--normalize-apostrophes', '--no-normalize-apostrophes'] }
592
+
593
+ it "must set #builder_options[:normalize_apostrophes] to false" do
594
+ expect(subject.builder_options[:normalize_apostrophes]).to be(false)
595
+ end
596
+ end
597
+ end
598
+
599
+ context "when given --normalize-acronyms" do
600
+ let(:argv) { ['--normalize-acronyms'] }
601
+
602
+ before { subject.option_parser.parse(argv) }
603
+
604
+ it "must set #builder_options[:normalize_acronyms] to true" do
605
+ expect(subject.builder_options[:normalize_acronyms]).to be(true)
606
+ end
607
+
608
+ context "and when given --no-normalize-acronyms" do
609
+ let(:argv) { ['--normalize-acronyms', '--no-normalize-acronyms'] }
610
+
611
+ it "must set #builder_options[:normalize_acronyms] to false" do
612
+ expect(subject.builder_options[:normalize_acronyms]).to be(false)
613
+ end
614
+ end
615
+ end
616
+
617
+ %w[-V --version].each do |flag|
618
+ context "when given #{flag}" do
619
+ let(:argv) { [flag] }
620
+
621
+ it "must print the program name and the Wordlist::VERSION" do
622
+ expect(subject).to receive(:exit)
623
+
624
+ expect {
625
+ subject.option_parser.parse(argv)
626
+ }.to output("#{described_class::PROGRAM_NAME} #{Wordlist::VERSION}#{$/}").to_stdout
627
+ end
628
+ end
629
+ end
630
+
631
+ %w[-h --help].each do |flag|
632
+ context "when given #{flag}" do
633
+ let(:argv) { [flag] }
634
+
635
+ it "must print the option paresr --help output" do
636
+ expect(subject).to receive(:exit)
637
+
638
+ expect {
639
+ subject.option_parser.parse(argv)
640
+ }.to output("#{subject.option_parser}").to_stdout
641
+ end
642
+ end
643
+ end
644
+ end
645
+ end
646
+
647
+ describe ".run" do
648
+ subject { described_class }
649
+
650
+ context "when Interrupt is raised" do
651
+ before do
652
+ expect_any_instance_of(described_class).to receive(:run).and_raise(Interrupt)
653
+ end
654
+
655
+ it "must exit with 130" do
656
+ expect(subject.run([])).to eq(130)
657
+ end
658
+ end
659
+
660
+ context "when Errno::EPIPE is raised" do
661
+ before do
662
+ expect_any_instance_of(described_class).to receive(:run).and_raise(Errno::EPIPE)
663
+ end
664
+
665
+ it "must exit with 0" do
666
+ expect(subject.run([])).to eq(0)
667
+ end
668
+ end
669
+ end
670
+
671
+ describe "#run" do
672
+ context "when given a wordlist file" do
673
+ let(:file) { ::File.join(fixtures_dir,'wordlist.txt') }
674
+ let(:argv) { [file] }
675
+
676
+ let(:expected_words) { File.readlines(file).map(&:chomp) }
677
+
678
+ it "must read each word from the file and print it to stdout" do
679
+ expect {
680
+ subject.run(argv)
681
+ }.to output(
682
+ expected_words.join($/) + $/
683
+ ).to_stdout
684
+ end
685
+
686
+ context "when also given the --exec COMMAND option" do
687
+ let(:command) { 'echo "WORD: {}"' }
688
+ let(:argv) { ["--exec", command, file] }
689
+
690
+ let(:expected_output) do
691
+ expected_words.map do |word|
692
+ end
693
+ end
694
+
695
+ it "must execute the command with each word from the wordlist" do
696
+ expected_words.each do |word|
697
+ expect(subject).to receive(:system).with(command.sub('{}',word))
698
+ end
699
+
700
+ subject.run(argv)
701
+ end
702
+ end
703
+ end
704
+
705
+ context "when given the --build option" do
706
+ let(:expected_words) { %w[foo bar baz qux] }
707
+ let(:text) { (expected_words * 100).shuffle.join(' ') }
708
+
709
+ let(:output) { File.join(fixtures_dir,'new_wordlist.txt') }
710
+
711
+ context "and given one input file" do
712
+ let(:input) { File.join(fixtures_dir,"input_file.txt") }
713
+ let(:argv) { ["--build", output, input] }
714
+
715
+ before { File.write(input,text) }
716
+
717
+ it "must build a new wordlist file based on the given file" do
718
+ subject.run(argv)
719
+
720
+ expect(File.readlines(output).map(&:chomp)).to match_array(expected_words)
721
+ end
722
+
723
+ after { FileUtils.rm_f(input) }
724
+ end
725
+
726
+ context "and given multiple input files" do
727
+ let(:words) { (expected_words * 100).shuffle }
728
+ let(:text1) { words[0,50] }
729
+ let(:text2) { words[50,50] }
730
+
731
+ let(:input1) { File.join(fixtures_dir,"input_file1.txt") }
732
+ let(:input2) { File.join(fixtures_dir,"input_file2.txt") }
733
+ let(:argv) { ["--build", output, input1, input2] }
734
+
735
+ before do
736
+ File.write(input1,text1)
737
+ File.write(input2,text2)
738
+ end
739
+
740
+ it "must build a new wordlist file based on the given files" do
741
+ subject.run(argv)
742
+
743
+ expect(File.readlines(output).map(&:chomp)).to match_array(expected_words)
744
+ end
745
+
746
+ after do
747
+ FileUtils.rm_f(input1)
748
+ FileUtils.rm_f(input2)
749
+ end
750
+ end
751
+
752
+ context "and given no input files" do
753
+ let(:argv) { ["--build", output] }
754
+
755
+ before do
756
+ $stdin = StringIO.new(text)
757
+ end
758
+
759
+ it "must build a new wordlist file by reading stdin" do
760
+ subject.run(argv)
761
+
762
+ expect(File.readlines(output).map(&:chomp)).to match_array(expected_words)
763
+ end
764
+
765
+ after do
766
+ $stdin = STDIN
767
+ end
768
+ end
769
+
770
+ after { FileUtils.rm_f(output) }
771
+ end
772
+
773
+ context "when an invalid option is given" do
774
+ let(:opt) { '--foo' }
775
+
776
+ it "must print 'wordlist: invalid option ...' to $stderr and exit with -1" do
777
+ expect {
778
+ expect(subject.run([opt])).to eq(-1)
779
+ }.to output("wordlist: invalid option: #{opt}#{$/}").to_stderr
780
+ end
781
+ end
782
+
783
+ context "when another type of Exception is raised" do
784
+ let(:exception) { RuntimeError.new("error!") }
785
+
786
+ before do
787
+ expect(subject).to receive(:read_mode).and_raise(exception)
788
+ end
789
+
790
+ it "must print a backtrace and exit with -1" do
791
+ expect {
792
+ expect(subject.run([])).to eq(-1)
793
+ }.to output(
794
+ %r{Oops! Looks like you've found a bug!
795
+ Please report the following text to: #{Regexp.escape(described_class::BUG_REPORT_URL)}
796
+
797
+ ```}m
798
+ ).to_stderr
799
+ end
800
+ end
801
+ end
802
+ end