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