string_pattern 2.1.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,1029 +1,1028 @@
1
- SP_ADD_TO_RUBY = true if !defined?(SP_ADD_TO_RUBY)
2
- require_relative 'string/pattern/add_to_ruby' if SP_ADD_TO_RUBY
3
-
4
- # SP_ADD_TO_RUBY: (TrueFalse, default: true) You need to add this constant value before requiring the library if you want to modify the default.
5
- # If true it will add 'generate' and 'validate' methods to the classes: Array, String and Symbol. Also it will add 'generate' method to Kernel
6
- # aliases: 'gen' for 'generate' and 'val' for 'validate'
7
- # Examples of use:
8
- # "(,3:N,) ,3:N,-,2:N,-,2:N".split(",").generate #>(937) 980-65-05
9
- # %w{( 3:N ) 1:_ 3:N - 2:N - 2:N}.gen #>(045) 448-63-09
10
- # ["1:L", "5-10:LN", "-", "3:N"].gen #>zqWihV-746
11
- # gen("10:N") #>3433409877
12
- # "20-30:@".gen #>dkj34MljjJD-df@jfdluul.dfu
13
- # "10:L/N/[/-./%d%]".validate("12ds6f--.s") #>[:value, :string_set_not_allowed]
14
- # "20-40:@".validate(my_email)
15
- # national_chars: (Array, default: english alphabet)
16
- # Set of characters that will be used when using T pattern
17
- # optimistic: (TrueFalse, default: true)
18
- # If true it will check on the strings of the array positions if they have the pattern format and assume in that case that is a pattern.
19
- # dont_repeat: (TrueFalse, default: false)
20
- # If you want to generate for example 1000 strings and be sure all those strings are different you can set it to true
21
- # default_infinite: (Integer, default: 10)
22
- # In case using regular expressions the maximum when using * or + for repetitions
23
- class StringPattern
24
- class << self
25
- attr_accessor :national_chars, :optimistic, :dont_repeat, :cache, :cache_values, :default_infinite
26
- end
27
- @national_chars = (('a'..'z').to_a + ('A'..'Z').to_a).join
28
- @optimistic = true
29
- @cache = Hash.new()
30
- @cache_values = Hash.new()
31
- @dont_repeat = false
32
- @default_infinite = 10
33
- NUMBER_SET = ('0'..'9').to_a
34
- SPECIAL_SET = [' ', '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', '_', '+', '=', '{', '}', '[', ']', "'", ';', ':', '?', '>', '<', '`', '|', '/', '"']
35
- ALPHA_SET_LOWER = ('a'..'z').to_a
36
- ALPHA_SET_CAPITAL = ('A'..'Z').to_a
37
- @palabras = []
38
- @palabras_camel = []
39
- @words = []
40
- @words_camel = []
41
- @palabras_short = []
42
- @words_short = []
43
- @palabras_camel_short = []
44
- @words_camel_short = []
45
-
46
- Pattern = Struct.new(:min_length, :max_length, :symbol_type, :required_data, :excluded_data, :data_provided,
47
- :string_set, :all_characters_set, :unique)
48
-
49
- ###############################################
50
- # Analyze the pattern supplied and returns an object of Pattern structure including:
51
- # min_length, max_length, symbol_type, required_data, excluded_data, data_provided, string_set, all_characters_set
52
- ###############################################
53
- def StringPattern.analyze(pattern, silent: false)
54
- return @cache[pattern.to_s] unless @cache[pattern.to_s].nil?
55
- min_length, max_length, symbol_type = pattern.to_s.scan(/(\d+)-(\d+):(.+)/)[0]
56
- if min_length.nil?
57
- min_length, symbol_type = pattern.to_s.scan(/^!?(\d+):(.+)/)[0]
58
- max_length = min_length
59
- if min_length.nil?
60
- puts "pattern argument not valid on StringPattern.generate: #{pattern.inspect}" unless silent
61
- return pattern.to_s
62
- end
63
- end
64
- if symbol_type[-1]=="&"
65
- symbol_type.chop!
66
- unique = true
67
- else
68
- unique = false
69
- end
70
-
71
- symbol_type = '!' + symbol_type if pattern.to_s[0] == '!'
72
- min_length = min_length.to_i
73
- max_length = max_length.to_i
74
-
75
- required_data = Array.new
76
- excluded_data = Array.new
77
- required = false
78
- excluded = false
79
- data_provided = Array.new
80
- a = symbol_type
81
- begin_provided = a.index('[')
82
- excluded_end_tag = false
83
- unless begin_provided.nil?
84
- c = begin_provided + 1
85
- until c == a.size or (a[c..c] == ']' and a[c..c + 1] != ']]')
86
- if a[c..c + 1] == ']]'
87
- data_provided.push(']')
88
- c = c + 2
89
- elsif a[c..c + 1] == '%%' and !excluded then
90
- data_provided.push('%')
91
- c = c + 2
92
- else
93
- if a[c..c] == '/' and !excluded
94
- if a[c..c + 1] == '//'
95
- data_provided.push(a[c..c])
96
- if required
97
- required_data.push([a[c..c]])
98
- end
99
- c = c + 1
100
- else
101
- if !required
102
- required = true
103
- else
104
- required = false
105
- end
106
- end
107
- else
108
- if required
109
- required_data.push([a[c..c]])
110
- else
111
- if a[c..c] == '%'
112
- if a[c..c + 1] == '%%' and excluded
113
- excluded_data.push([a[c..c]])
114
- c = c + 1
115
- else
116
- if !excluded
117
- excluded = true
118
- else
119
- excluded = false
120
- excluded_end_tag = true
121
- end
122
- end
123
- else
124
- if excluded
125
- excluded_data.push([a[c..c]])
126
- end
127
- end
128
-
129
- end
130
- if excluded == false and excluded_end_tag == false
131
- data_provided.push(a[c..c])
132
- end
133
- excluded_end_tag = false
134
- end
135
- c = c + 1
136
- end
137
- end
138
- symbol_type = symbol_type[0..begin_provided].to_s + symbol_type[c..symbol_type.size].to_s
139
- end
140
-
141
- required = false
142
- required_symbol = ''
143
- if symbol_type.include?("/")
144
- symbol_type.chars.each {|stc|
145
- if stc == '/'
146
- if !required
147
- required = true
148
- else
149
- required = false
150
- end
151
- else
152
- if required
153
- required_symbol += stc
154
- end
155
- end
156
- }
157
- end
158
-
159
- national_set = @national_chars.chars
160
-
161
- if symbol_type.include?('L') then
162
- alpha_set = ALPHA_SET_LOWER + ALPHA_SET_CAPITAL
163
- elsif symbol_type.include?('x')
164
- alpha_set = ALPHA_SET_LOWER
165
- if symbol_type.include?('X')
166
- alpha_set = alpha_set + ALPHA_SET_CAPITAL
167
- end
168
- elsif symbol_type.include?('X')
169
- alpha_set = ALPHA_SET_CAPITAL
170
- else
171
- alpha_set = []
172
- end
173
- if symbol_type.include?('T')
174
- alpha_set = alpha_set + national_set
175
- end
176
-
177
- unless required_symbol.nil?
178
- if required_symbol.include?('x')
179
- required_data.push ALPHA_SET_LOWER
180
- end
181
- if required_symbol.include?('X')
182
- required_data.push ALPHA_SET_CAPITAL
183
- end
184
- if required_symbol.include?('L')
185
- required_data.push(ALPHA_SET_CAPITAL + ALPHA_SET_LOWER)
186
- end
187
- if required_symbol.include?('T')
188
- required_data.push national_set
189
- end
190
- required_symbol = required_symbol.downcase
191
- end
192
- string_set = Array.new
193
- all_characters_set = ALPHA_SET_CAPITAL + ALPHA_SET_LOWER + NUMBER_SET + SPECIAL_SET + data_provided + national_set
194
-
195
- if symbol_type.include?('_')
196
- unless symbol_type.include?('$')
197
- string_set.push(' ')
198
- end
199
- if required_symbol.include?('_')
200
- required_data.push([' '])
201
- end
202
- end
203
-
204
- #symbol_type = symbol_type.downcase
205
-
206
- if symbol_type.downcase.include?('x') or symbol_type.downcase.include?('l') or symbol_type.downcase.include?('t')
207
- string_set = string_set + alpha_set
208
- end
209
- if symbol_type.downcase.include?('n')
210
- string_set = string_set + NUMBER_SET
211
- end
212
- if symbol_type.include?('$')
213
- string_set = string_set + SPECIAL_SET
214
- end
215
- if symbol_type.include?('*')
216
- string_set = string_set + all_characters_set
217
- end
218
- if data_provided.size != 0
219
- string_set = string_set + data_provided
220
- end
221
- unless required_symbol.empty?
222
- if required_symbol.include?('n')
223
- required_data.push NUMBER_SET
224
- end
225
- if required_symbol.include?('$')
226
- required_data.push SPECIAL_SET
227
- end
228
- end
229
- unless excluded_data.empty?
230
- string_set = string_set - excluded_data.flatten
231
- end
232
- string_set.uniq!
233
- @cache[pattern.to_s] = Pattern.new(min_length, max_length, symbol_type, required_data, excluded_data, data_provided,
234
- string_set, all_characters_set, unique)
235
- return @cache[pattern.to_s]
236
- end
237
-
238
- ###############################################
239
- # Generate a random string based on the pattern supplied
240
- # (if SP_ADD_TO_RUBY==true, by default is true) To simplify its use it is part of the String, Array, Symbol and Kernel Ruby so can be easily used also like this:
241
- # "10-15:Ln/x/".generate #generate method on String class (alias: gen)
242
- # ['(', :'3:N', ')', :'6-8:N'].generate #generate method on Array class (alias: gen)
243
- # generate("10-15:Ln/x/") #generate Ruby Kernel method
244
- # generate(['(', :'3:N', ')', :'6-8:N']) #generate Ruby Kernel method
245
- # "(,3:N,) ,3:N,-,2:N,-,2:N".split(",").generate #>(937) #generate method on Array class (alias: gen)
246
- # %w{( 3:N ) 1:_ 3:N - 2:N - 2:N}.gen #generate method on Array class, using alias gen method
247
- # Input:
248
- # pattern: array or string of different patterns. A pattern is a string with this info:
249
- # "length:symbol_type" or "min_length-max_length:symbol_type"
250
- # In case an array supplied, the positions using a string pattern should be supplied as symbols if StringPattern.optimistic==false
251
- #
252
- # These are the possible string patterns you will be able to supply:
253
- # If at the beginning we supply the character ! the resulting string won't fulfill the pattern. This character need to be the first character of the pattern.
254
- # min_length -- minimum length of the string
255
- # max_length (optional) -- maximum length of the string. If not provided the result will be with the min_length provided
256
- # symbol_type -- the type of the string we want.
257
- # you can use a combination of any ot these:
258
- # x for alpha in lowercase
259
- # X for alpha in capital letters
260
- # L for all kind of alpha in capital and lower letters
261
- # T for the national characters defined on StringPattern.national_chars
262
- # n for number
263
- # $ for special characters (includes space)
264
- # _ for space
265
- # * all characters
266
- # [characters] the characters we want. If we want to add also the ] character you have to write: ]]. If we want to add also the % character you have to write: %%
267
- # %characters% the characters we don't want on the resulting string. %% to exclude the character %
268
- # /symbols or characters/ If we want these characters to be included on the resulting string. If we want to add also the / character you have to write: //
269
- # We can supply 0 to allow empty strings, this character need to be at the beginning
270
- # If you want to include the character " use \"
271
- # If you want to include the character \ use \\
272
- # If you want to include the character [ use \[
273
- # Other uses:
274
- # @ for email
275
- # W for English words, capital and lower
276
- # w for English words only lower and words separated by underscore
277
- # P for Spanish words, capital and lower
278
- # p for Spanish words only lower and words separated by underscore
279
- # Examples:
280
- # [:"6:X", :"3-8:_N"]
281
- # # it will return a string starting with 6 capital letters and then a string containing numbers and space from 3 to 8 characters, for example: "LDJKKD34 555"
282
- # [:"6-15:L_N", "fixed text", :"3:N"]
283
- # # it will return a string of 6-15 characters containing Letters-spaces-numbers, then the text: 'fixed text' and at the end a string of 3 characters containing numbers, for example: ["L_N",6,15],"fixed text",["N",3] "3 Am399 afixed text882"
284
- # "10-20:LN[=#]"
285
- # # it will return a string of 10-20 characters containing Letters and/or numbers and/or the characters = and #, for example: eiyweQFWeL#do4Vl
286
- # "30:TN_[#=]/x/"
287
- # # it will return a string of 30 characters containing national characters defined on StringPattern.national_chars and/or numbers and/or spaces and/or the characters # = and it is necessary the resultant string includes lower alpha chars. For example: HaEdQTzJ3=OtXMh1mAPqv7NCy=upLy
288
- # "10:N[%0%]"
289
- # # 10 characters length containing numbers and excluding the character 0, for example: 3523497757
290
- # "10:N[%0%/AB/]"
291
- # # 10 characters length containing numbers and excluding the character 0 and necessary to contain the characters A B, for example: 3AA4AA57BB
292
- # "!10:N[%0%/AB/]"
293
- # # it will generate a string that doesn't fulfill the pattern supplied, examples:
294
- # # a6oMQ4JK9g
295
- # # /Y<N6Aa[ae
296
- # # 3444439A34B32
297
- # "10:N[%0%/AB/]", errors: [:length]
298
- # # it will generate a string following the pattern and with the errors supplied, in this case, length, example: AB44
299
- # Output:
300
- # the generated string
301
- ###############################################
302
- def StringPattern.generate(pattern, expected_errors: [], **synonyms)
303
- tries = 0
304
- begin
305
- good_result = true
306
- tries += 1
307
- string = ''
308
-
309
- expected_errors = synonyms[:errors] if synonyms.keys.include?(:errors)
310
-
311
- if expected_errors.kind_of?(Symbol)
312
- expected_errors = [expected_errors]
313
- end
314
-
315
- if pattern.kind_of?(Array)
316
- pattern.each {|pat|
317
-
318
- if pat.kind_of?(Array) # for the case it is one of the values
319
- pat = pat.sample
320
- end
321
-
322
- if pat.kind_of?(Symbol)
323
- if pat.to_s.scan(/^!?\d+-?\d*:.+/).size > 0
324
- string << StringPattern.generate(pat.to_s, expected_errors: expected_errors)
325
- else
326
- string << pat.to_s
327
- end
328
- elsif pat.kind_of?(String) then
329
- if @optimistic and pat.to_s.scan(/^!?\d+-?\d*:.+/).size > 0
330
- string << StringPattern.generate(pat.to_s, expected_errors: expected_errors)
331
- else
332
- string << pat
333
- end
334
- else
335
- puts "StringPattern.generate: it seems you supplied wrong array of patterns: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
336
- return ''
337
- end
338
- }
339
- return string
340
- elsif pattern.kind_of?(String) or pattern.kind_of?(Symbol)
341
- patt = StringPattern.analyze(pattern)
342
- min_length = patt.min_length
343
- max_length = patt.max_length
344
- symbol_type = patt.symbol_type
345
-
346
- required_data = patt.required_data
347
- excluded_data = patt.excluded_data
348
- string_set = patt.string_set
349
- all_characters_set = patt.all_characters_set
350
-
351
- required_chars = Array.new
352
- unless required_data.size == 0
353
- required_data.each {|rd|
354
- required_chars << rd if rd.size == 1
355
- }
356
- unless excluded_data.size == 0
357
- if (required_chars.flatten & excluded_data.flatten).size > 0
358
- puts "pattern argument not valid on StringPattern.generate, a character cannot be required and excluded at the same time: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
359
- return ''
360
- end
361
- end
362
- end
363
- string_set_not_allowed = Array.new
364
- elsif pattern.kind_of?(Regexp)
365
- return generate(pattern.to_sp, expected_errors: expected_errors)
366
- else
367
- puts "pattern argument not valid on StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
368
- return pattern.to_s
369
- end
370
-
371
-
372
- allow_empty = false
373
- deny_pattern = false
374
- if symbol_type[0..0] == '!'
375
- deny_pattern = true
376
- possible_errors = [:length, :value, :required_data, :excluded_data, :string_set_not_allowed]
377
- (rand(possible_errors.size) + 1).times {
378
- expected_errors << possible_errors.sample
379
- }
380
- expected_errors.uniq!
381
- if symbol_type[1..1] == '0'
382
- allow_empty = true
383
- end
384
- elsif symbol_type[0..0] == '0' then
385
- allow_empty = true
386
- end
387
-
388
- if expected_errors.include?(:min_length) or expected_errors.include?(:length) or
389
- expected_errors.include?(:max_length)
390
- allow_empty = !allow_empty
391
- elsif expected_errors.include?(:value) or
392
- expected_errors.include?(:excluded_data) or
393
- expected_errors.include?(:required_data) or
394
- expected_errors.include?(:string_set_not_allowed) and allow_empty
395
- allow_empty = false
396
- end
397
-
398
- length = min_length
399
- symbol_type_orig = symbol_type
400
-
401
- expected_errors_left = expected_errors.dup
402
-
403
- symbol_type = symbol_type_orig
404
-
405
- unless deny_pattern
406
- if required_data.size == 0 and expected_errors_left.include?(:required_data)
407
- puts "required data not supplied on pattern so it won't be possible to generate a wrong string. StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
408
- return ''
409
- end
410
-
411
- if excluded_data.size == 0 and expected_errors_left.include?(:excluded_data)
412
- puts "excluded data not supplied on pattern so it won't be possible to generate a wrong string. StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
413
- return ''
414
- end
415
-
416
- if expected_errors_left.include?(:string_set_not_allowed)
417
- string_set_not_allowed = all_characters_set - string_set
418
- if string_set_not_allowed.size == 0 then
419
- puts "all characters are allowed so it won't be possible to generate a wrong string. StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
420
- return ''
421
- end
422
- end
423
- end
424
-
425
- if expected_errors_left.include?(:min_length) or
426
- expected_errors_left.include?(:max_length) or
427
- expected_errors_left.include?(:length)
428
- if expected_errors_left.include?(:min_length) or
429
- (min_length > 0 and expected_errors_left.include?(:length) and rand(2) == 0)
430
- if min_length > 0
431
- if allow_empty
432
- length = rand(min_length).to_i
433
- else
434
- length = rand(min_length - 1).to_i + 1
435
- end
436
- if required_data.size > length and required_data.size < min_length
437
- length = required_data.size
438
- end
439
- expected_errors_left.delete(:length)
440
- expected_errors_left.delete(:min_length)
441
- else
442
- puts "min_length is 0 so it won't be possible to generate a wrong string smaller than 0 characters. StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
443
- return ''
444
- end
445
- elsif expected_errors_left.include?(:max_length) or expected_errors_left.include?(:length)
446
- length = max_length + 1 + rand(max_length).to_i
447
- expected_errors_left.delete(:length)
448
- expected_errors_left.delete(:max_length)
449
- end
450
- else
451
- if allow_empty and rand(7) == 1
452
- length = 0
453
- else
454
- if max_length == min_length
455
- length = min_length
456
- else
457
- length = min_length + rand(max_length - min_length + 1)
458
- end
459
- end
460
- end
461
-
462
- if deny_pattern
463
- if required_data.size == 0 and expected_errors_left.include?(:required_data)
464
- expected_errors_left.delete(:required_data)
465
- end
466
-
467
- if excluded_data.size == 0 and expected_errors_left.include?(:excluded_data)
468
- expected_errors_left.delete(:excluded_data)
469
- end
470
-
471
- if expected_errors_left.include?(:string_set_not_allowed)
472
- string_set_not_allowed = all_characters_set - string_set
473
- if string_set_not_allowed.size == 0
474
- expected_errors_left.delete(:string_set_not_allowed)
475
- end
476
- end
477
-
478
- if symbol_type == '!@' and expected_errors_left.size == 0 and !expected_errors.include?(:length) and
479
- (expected_errors.include?(:required_data) or expected_errors.include?(:excluded_data))
480
- expected_errors_left.push(:value)
481
- end
482
-
483
- end
484
-
485
- string = ''
486
- if symbol_type != '@' and symbol_type != '!@' and length != 0 and string_set.size != 0
487
- if string_set.size != 0
488
- 1.upto(length) {|i| string << string_set.sample.to_s
489
- }
490
- end
491
- if required_data.size > 0
492
- positions_to_set = (0..(string.size - 1)).to_a
493
- required_data.each {|rd|
494
- if (string.chars & rd).size > 0
495
- rd_to_set = (string.chars & rd).sample
496
- else
497
- rd_to_set = rd.sample
498
- end
499
- if ((0...string.length).find_all {|i| string[i, 1] == rd_to_set}).size == 0
500
- if positions_to_set.size == 0
501
- puts "pattern not valid on StringPattern.generate, not possible to generate a valid string: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
502
- return ''
503
- else
504
- k = positions_to_set.sample
505
- string[k] = rd_to_set
506
- positions_to_set.delete(k)
507
- end
508
- else
509
- k = ((0...string.length).find_all {|i| string[i, 1] == rd_to_set}).sample
510
- positions_to_set.delete(k)
511
- end
512
- }
513
- end
514
- excluded_data.each {|ed|
515
- if (string.chars & ed).size > 0
516
- (string.chars & ed).each {|s|
517
- string.gsub!(s, string_set.sample)
518
- }
519
- end
520
- }
521
-
522
- if expected_errors_left.include?(:value)
523
- string_set_not_allowed = all_characters_set - string_set if string_set_not_allowed.size == 0
524
- if string_set_not_allowed.size == 0
525
- puts "Not possible to generate a non valid string on StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
526
- return ''
527
- end
528
- (rand(string.size) + 1).times {
529
- string[rand(string.size)] = (all_characters_set - string_set).sample
530
- }
531
- expected_errors_left.delete(:value)
532
- end
533
-
534
- if expected_errors_left.include?(:required_data) and required_data.size > 0
535
- (rand(required_data.size) + 1).times {
536
- chars_to_remove = required_data.sample
537
- chars_to_remove.each {|char_to_remove|
538
- string.gsub!(char_to_remove, (string_set - chars_to_remove).sample)
539
- }
540
- }
541
- expected_errors_left.delete(:required_data)
542
- end
543
-
544
- if expected_errors_left.include?(:excluded_data) and excluded_data.size > 0
545
- (rand(string.size) + 1).times {
546
- string[rand(string.size)] = excluded_data.sample.sample
547
- }
548
- expected_errors_left.delete(:excluded_data)
549
- end
550
-
551
- if expected_errors_left.include?(:string_set_not_allowed)
552
- string_set_not_allowed = all_characters_set - string_set if string_set_not_allowed.size == 0
553
- if string_set_not_allowed.size > 0
554
- (rand(string.size) + 1).times {
555
- string[rand(string.size)] = string_set_not_allowed.sample
556
- }
557
- expected_errors_left.delete(:string_set_not_allowed)
558
- end
559
- end
560
- elsif (symbol_type == 'W' or symbol_type == 'P' or symbol_type=='w' or symbol_type =='p') and length>0
561
- #jal9
562
- words = []
563
- words_short = []
564
- if symbol_type == 'W'
565
- if @words_camel.empty?
566
- require "pathname"
567
- require "json"
568
- filename = File.join Pathname(File.dirname(__FILE__)), "../data", "english/nouns.json"
569
- nouns = JSON.parse(File.read(filename))
570
- filename = File.join Pathname(File.dirname(__FILE__)), "../data", "english/adjs.json"
571
- adjs = JSON.parse(File.read(filename))
572
- nouns = nouns.map(&:to_camel_case)
573
- adjs = adjs.map(&:to_camel_case)
574
- @words_camel = adjs + nouns
575
- @words_camel_short = @words_camel.sample(1000)
576
- end
577
- words = @words_camel
578
- words_short = @words_camel_short
579
- elsif symbol_type == 'w'
580
- if @words.empty?
581
- require "pathname"
582
- require "json"
583
- filename = File.join Pathname(File.dirname(__FILE__)), "../data", "english/nouns.json"
584
- nouns = JSON.parse(File.read(filename))
585
- filename = File.join Pathname(File.dirname(__FILE__)), "../data", "english/adjs.json"
586
- adjs = JSON.parse(File.read(filename))
587
- @words = adjs + nouns
588
- @words_short = @words.sample(1000)
589
- end
590
- words = @words
591
- words_short = @words_short
592
- elsif symbol_type == 'P'
593
- if @palabras_camel.empty?
594
- require "pathname"
595
- require "json"
596
- filename = File.join Pathname(File.dirname(__FILE__)), "../data", "spanish/palabras.json"
597
- palabras = JSON.parse(File.read(filename))
598
- palabras = palabras.map(&:to_camel_case)
599
- @palabras_camel = palabras
600
- @palabras_camel_short = @palabras_camel.sample(1000)
601
- end
602
- words = @palabras_camel
603
- words_short = @palabras_camel_short
604
- elsif symbol_type == 'p'
605
- if @palabras.empty?
606
- require "pathname"
607
- require "json"
608
- filename = File.join Pathname(File.dirname(__FILE__)), "../data", "spanish/palabras.json"
609
- palabras = JSON.parse(File.read(filename))
610
- @palabras = palabras
611
- @palabras_short = @palabras.sample(1000)
612
- end
613
- words = @palabras
614
- words_short = @palabras_short
615
- end
616
-
617
- wordr = ""
618
- wordr_array = []
619
- tries = 0
620
- while wordr.length < min_length
621
- tries += 1
622
- length = rand(max_length - wordr.length)+1
623
- if (tries % 100) == 0
624
- words_short = words.sample(1000)
625
- end
626
- if tries > 1000
627
- wordr += 'A'*length
628
- break
629
- end
630
- if symbol_type == 'w' or symbol_type == "p"
631
- res = (words_short.select { |word| word.length == length }).sample.to_s
632
- unless res.to_s==''
633
- wordr_array<<res
634
- wordr = wordr_array.join("_")
635
- end
636
- else
637
- wordr += (words_short.select { |word| word.length == length }).sample.to_s
638
- end
639
- end
640
- good_result = true
641
- string = wordr
642
-
643
- elsif (symbol_type == '@' or symbol_type == '!@') and length > 0
644
- if min_length > 6 and length < 6
645
- length = 6
646
- end
647
- if deny_pattern and
648
- (expected_errors.include?(:required_data) or expected_errors.include?(:excluded_data) or
649
- expected_errors.include?(:string_set_not_allowed))
650
- expected_errors_left.push(:value)
651
- expected_errors.push(:value)
652
- expected_errors.uniq!
653
- expected_errors_left.uniq!
654
- end
655
-
656
- expected_errors_left_orig = expected_errors_left.dup
657
- tries = 0
658
- begin
659
- expected_errors_left = expected_errors_left_orig.dup
660
- tries += 1
661
- string = ''
662
- alpha_set = ALPHA_SET_LOWER + ALPHA_SET_CAPITAL
663
- string_set = alpha_set + NUMBER_SET + ['.'] + ['_'] + ['-']
664
- string_set_not_allowed = all_characters_set - string_set
665
-
666
- extension = '.'
667
- at_sign = '@'
668
-
669
- if expected_errors_left.include?(:value)
670
- if rand(2) == 1
671
- extension = (all_characters_set - ['.']).sample
672
- expected_errors_left.delete(:value)
673
- expected_errors_left.delete(:required_data)
674
- end
675
- if rand(2) == 1
676
- 1.upto(rand(7)) {|i| extension << alpha_set.sample.downcase
677
- }
678
- (rand(extension.size) + 1).times {
679
- extension[rand(extension.size)] = (string_set - alpha_set - ['.']).sample
680
- }
681
- expected_errors_left.delete(:value)
682
- else
683
- 1.upto(rand(3) + 2) {|i| extension << alpha_set.sample.downcase
684
- }
685
- end
686
- if rand(2) == 1
687
- at_sign = (string_set - ['@']).sample
688
- expected_errors_left.delete(:value)
689
- expected_errors_left.delete(:required_data)
690
- end
691
- else
692
- if length > 6
693
- 1.upto(rand(3) + 2) {|i| extension << alpha_set.sample.downcase
694
- }
695
- else
696
- 1.upto(2) {|i| extension << alpha_set.sample.downcase
697
- }
698
- end
699
- end
700
- length_e = length - extension.size - 1
701
- length1 = rand(length_e - 1) + 1
702
- length2 = length_e - length1
703
- 1.upto(length1) {|i| string << string_set.sample}
704
-
705
- string << at_sign
706
-
707
- domain = ''
708
- domain_set = alpha_set + NUMBER_SET + ['.'] + ['-']
709
- 1.upto(length2) {|i| domain << domain_set.sample.downcase
710
- }
711
-
712
- if expected_errors.include?(:value) and rand(2) == 1 and domain.size > 0
713
- (rand(domain.size) + 1).times {
714
- domain[rand(domain.size)] = (all_characters_set - domain_set).sample
715
- }
716
- expected_errors_left.delete(:value)
717
- end
718
- string << domain << extension
719
-
720
- if expected_errors_left.include?(:value) or expected_errors_left.include?(:string_set_not_allowed)
721
- (rand(string.size) + 1).times {
722
- string[rand(string.size)] = string_set_not_allowed.sample
723
- }
724
- expected_errors_left.delete(:value)
725
- expected_errors_left.delete(:string_set_not_allowed)
726
- end
727
-
728
- error_regular_expression = false
729
-
730
- if deny_pattern and expected_errors.include?(:length)
731
- good_result = true #it is already with wrong length
732
- else
733
- # I'm doing this because many times the regular expression checking hangs with these characters
734
- wrong = %w(.. __ -- ._ _. .- -. _- -_ @. @_ @- .@ _@ -@ @@)
735
- if !(Regexp.union(*wrong) === string) #don't include any or the wrong strings
736
- if string.index('@').to_i > 0 and
737
- string[0..(string.index('@') - 1)].scan(/([a-z0-9]+([\+\._\-][a-z0-9]|)*)/i).join == string[0..(string.index('@') - 1)] and
738
- string[(string.index('@') + 1)..-1].scan(/([0-9a-z]+([\.-][a-z0-9]|)*)/i).join == string[string[(string.index('@') + 1)..-1]]
739
- error_regular_expression = false
740
- else
741
- error_regular_expression = true
742
- end
743
- else
744
- error_regular_expression = true
745
- end
746
-
747
- if expected_errors.size == 0
748
- if error_regular_expression
749
- good_result = false
750
- else
751
- good_result = true
752
- end
753
- elsif expected_errors_left.size == 0 and
754
- (expected_errors - [:length, :min_length, :max_length]).size == 0
755
- good_result = true
756
- elsif expected_errors != [:length]
757
- if !error_regular_expression
758
- good_result = false
759
- elsif expected_errors.include?(:value)
760
- good_result = true
761
- end
762
- end
763
- end
764
-
765
- end until good_result or tries > 100
766
- unless good_result
767
- puts "Not possible to generate an email on StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
768
- return ''
769
- end
770
- end
771
- if @dont_repeat
772
- if @cache_values[pattern.to_s].nil?
773
- @cache_values[pattern.to_s] = Array.new()
774
- @cache_values[pattern.to_s].push(string)
775
- good_result = true
776
- elsif @cache_values[pattern.to_s].include?(string)
777
- good_result = false
778
- else
779
- @cache_values[pattern.to_s].push(string)
780
- good_result = true
781
- end
782
- end
783
- if pattern.kind_of?(Symbol) and patt.unique
784
- if @cache_values[pattern.__id__].nil?
785
- @cache_values[pattern.__id__] = Array.new()
786
- @cache_values[pattern.__id__].push(string)
787
- good_result = true
788
- elsif @cache_values[pattern.__id__].include?(string)
789
- good_result = false
790
- else
791
- @cache_values[pattern.__id__].push(string)
792
- good_result = true
793
- end
794
- end
795
- end until good_result or tries > 10000
796
- unless good_result
797
- puts "Not possible to generate the string on StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
798
- puts "Take in consideration if you are using StringPattern.dont_repeat=true that you don't try to generate more strings that are possible to be generated"
799
- return ''
800
- end
801
-
802
- return string
803
- end
804
-
805
-
806
- ##############################################
807
- # This method is defined to validate if the text_to_validate supplied follows the pattern
808
- # It works also with array of patterns but in that case will return only true or false
809
- # input:
810
- # text (String) (synonyms: text_to_validate, validate) -- The text to validate
811
- # pattern -- symbol with this info: "length:symbol_type" or "min_length-max_length:symbol_type"
812
- # min_length -- minimum length of the string
813
- # max_length (optional) -- maximum length of the string. If not provided the result will be with the min_length provided
814
- # symbol_type -- the type of the string we want.
815
- # expected_errors (Array of symbols) (optional) (synonyms: errors) -- :length, :min_length, :max_length, :value, :required_data, :excluded_data, :string_set_not_allowed
816
- # not_expected_errors (Array of symbols) (optional) (synonyms: not_errors, non_expected_errors) -- :length, :min_length, :max_length, :value, :required_data, :excluded_data, :string_set_not_allowed
817
- # example:
818
- # validate(text: "This text will be validated", pattern: :"10-20:Xn", expected_errors: [:value, :max_length])
819
- #
820
- # Output:
821
- # if expected_errors and not_expected_errors are not supplied: an array with all detected errors
822
- # if expected_errors or not_expected_errors supplied: true or false
823
- # if array of patterns supplied, it will return true or false
824
- ###############################################
825
- def StringPattern.validate(text: '', pattern: '', expected_errors: [], not_expected_errors: [], **synonyms)
826
- text_to_validate = text
827
- text_to_validate = synonyms[:text_to_validate] if synonyms.keys.include?(:text_to_validate)
828
- text_to_validate = synonyms[:validate] if synonyms.keys.include?(:validate)
829
- expected_errors = synonyms[:errors] if synonyms.keys.include?(:errors)
830
- not_expected_errors = synonyms[:not_errors] if synonyms.keys.include?(:not_errors)
831
- not_expected_errors = synonyms[:non_expected_errors] if synonyms.keys.include?(:non_expected_errors)
832
- #:length, :min_length, :max_length, :value, :required_data, :excluded_data, :string_set_not_allowed
833
- if (expected_errors.include?(:min_length) or expected_errors.include?(:max_length)) and !expected_errors.include?(:length)
834
- expected_errors.push(:length)
835
- end
836
- if (not_expected_errors.include?(:min_length) or not_expected_errors.include?(:max_length)) and !not_expected_errors.include?(:length)
837
- not_expected_errors.push(:length)
838
- end
839
- if pattern.kind_of?(Array) and pattern.size == 1
840
- pattern = pattern[0]
841
- elsif pattern.kind_of?(Array) and pattern.size > 1 then
842
- total_min_length = 0
843
- total_max_length = 0
844
- all_errors_collected = Array.new
845
- result = true
846
- num_patt = 0
847
- patterns = Array.new
848
- pattern.each {|pat|
849
- if (pat.kind_of?(String) and (!StringPattern.optimistic or
850
- (StringPattern.optimistic and pat.to_s.scan(/(\d+)-(\d+):(.+)/).size == 0 and pat.to_s.scan(/^!?(\d+):(.+)/).size == 0))) #fixed text
851
- symbol_type = ''
852
- min_length = max_length = pat.length
853
- elsif pat.kind_of?(Symbol) or (pat.kind_of?(String) and StringPattern.optimistic and
854
- (pat.to_s.scan(/(\d+)-(\d+):(.+)/).size > 0 or pat.to_s.scan(/^!?(\d+):(.+)/).size > 0))
855
- patt = StringPattern.analyze(pat)
856
- min_length = patt.min_length
857
- max_length = patt.max_length
858
- symbol_type = patt.symbol_type
859
- else
860
- puts "String pattern class not supported (#{pat.class} for #{pat})"
861
- end
862
-
863
- patterns.push({pattern: pat, min_length: min_length, max_length: max_length, symbol_type: symbol_type})
864
-
865
- total_min_length += min_length
866
- total_max_length += max_length
867
-
868
- if num_patt == (pattern.size - 1) # i am in the last one
869
- if text_to_validate.length < total_min_length
870
- all_errors_collected.push(:length)
871
- all_errors_collected.push(:min_length)
872
- end
873
-
874
- if text_to_validate.length > total_max_length
875
- all_errors_collected.push(:length)
876
- all_errors_collected.push(:max_length)
877
- end
878
-
879
- end
880
- num_patt += 1
881
-
882
-
883
- }
884
-
885
- num_patt = 0
886
- patterns.each {|patt|
887
-
888
- tmp_result = false
889
- (patt[:min_length]..patt[:max_length]).each {|n|
890
- res = StringPattern.validate(text: text_to_validate[0..n - 1], pattern: patt[:pattern], not_expected_errors: not_expected_errors)
891
- if res.kind_of?(Array)
892
- all_errors_collected += res
893
- end
894
-
895
- if res.kind_of?(TrueClass) or (res.kind_of?(Array) and res.size == 0) #valid
896
- #we pass in the next one the rest of the pattern array list: pattern: pattern[num_patt+1..pattern.size]
897
- res = StringPattern.validate(text: text_to_validate[n..text_to_validate.length], pattern: pattern[num_patt + 1..pattern.size], expected_errors: expected_errors, not_expected_errors: not_expected_errors)
898
-
899
- if res.kind_of?(Array)
900
- if ((all_errors_collected + res) - expected_errors).size > 0
901
- tmp_result = false
902
- else
903
- all_errors_collected += res
904
- tmp_result = true
905
- end
906
- elsif res.kind_of?(TrueClass) then
907
- tmp_result = true
908
- end
909
- return true if tmp_result
910
- end
911
- }
912
-
913
- unless tmp_result
914
- return false
915
- end
916
- num_patt += 1
917
- }
918
- return result
919
- end
920
-
921
- if (pattern.kind_of?(String) and (!StringPattern.optimistic or
922
- (StringPattern.optimistic and pattern.to_s.scan(/(\d+)-(\d+):(.+)/).size == 0 and pattern.to_s.scan(/^!?(\d+):(.+)/).size == 0))) #fixed text
923
- symbol_type = ''
924
- min_length = max_length = pattern.length
925
- else #symbol
926
- patt = StringPattern.analyze(pattern)
927
- min_length = patt.min_length
928
- max_length = patt.max_length
929
- symbol_type = patt.symbol_type
930
-
931
- required_data = patt.required_data
932
- excluded_data = patt.excluded_data
933
- string_set = patt.string_set
934
- all_characters_set = patt.all_characters_set
935
-
936
- required_chars = Array.new
937
- required_data.each {|rd|
938
- required_chars << rd if rd.size == 1
939
- }
940
- if (required_chars.flatten & excluded_data.flatten).size > 0
941
- puts "pattern argument not valid on StringPattern.validate, a character cannot be required and excluded at the same time: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
942
- return ''
943
- end
944
-
945
- end
946
-
947
- if text_to_validate.nil?
948
- return false
949
- end
950
- detected_errors = Array.new
951
-
952
- if text_to_validate.length < min_length
953
- detected_errors.push(:min_length)
954
- detected_errors.push(:length)
955
- end
956
- if text_to_validate.length > max_length
957
- detected_errors.push(:max_length)
958
- detected_errors.push(:length)
959
- end
960
-
961
- if symbol_type == '' #fixed text
962
- if pattern.to_s != text.to_s #not equal
963
- detected_errors.push(:value)
964
- detected_errors.push(:required_data)
965
- end
966
- else # pattern supplied
967
- if symbol_type != '@'
968
- if required_data.size > 0
969
- required_data.each {|rd|
970
- if (text_to_validate.chars & rd).size == 0
971
- detected_errors.push(:value)
972
- detected_errors.push(:required_data)
973
- break
974
- end
975
- }
976
- end
977
- if excluded_data.size > 0
978
- if (excluded_data & text_to_validate.chars).size > 0
979
- detected_errors.push(:value)
980
- detected_errors.push(:excluded_data)
981
- end
982
- end
983
- string_set_not_allowed = all_characters_set - string_set
984
- text_to_validate.chars.each {|st|
985
- if string_set_not_allowed.include?(st)
986
- detected_errors.push(:value)
987
- detected_errors.push(:string_set_not_allowed)
988
- break
989
- end
990
- }
991
- else #symbol_type=="@"
992
- string = text_to_validate
993
- wrong = %w(.. __ -- ._ _. .- -. _- -_ @. @_ @- .@ _@ -@ @@)
994
- if !(Regexp.union(*wrong) === string) #don't include any or the wrong strings
995
- if string.index('@').to_i > 0 and
996
- string[0..(string.index('@') - 1)].scan(/([a-z0-9]+([\+\._\-][a-z0-9]|)*)/i).join == string[0..(string.index('@') - 1)] and
997
- string[(string.index('@') + 1)..-1].scan(/([0-9a-z]+([\.-][a-z0-9]|)*)/i).join == string[string[(string.index('@') + 1)..-1]]
998
- error_regular_expression = false
999
- else
1000
- error_regular_expression = true
1001
- end
1002
- else
1003
- error_regular_expression = true
1004
- end
1005
-
1006
- if error_regular_expression
1007
- detected_errors.push(:value)
1008
- end
1009
-
1010
- end
1011
- end
1012
-
1013
- if expected_errors.size == 0 and not_expected_errors.size == 0
1014
- return detected_errors
1015
- else
1016
- if expected_errors & detected_errors == expected_errors
1017
- if (not_expected_errors & detected_errors).size > 0
1018
- return false
1019
- else
1020
- return true
1021
- end
1022
- else
1023
- return false
1024
- end
1025
- end
1026
- end
1027
-
1028
- end
1029
-
1
+ SP_ADD_TO_RUBY = true if !defined?(SP_ADD_TO_RUBY)
2
+ require_relative 'string/pattern/add_to_ruby' if SP_ADD_TO_RUBY
3
+
4
+ # SP_ADD_TO_RUBY: (TrueFalse, default: true) You need to add this constant value before requiring the library if you want to modify the default.
5
+ # If true it will add 'generate' and 'validate' methods to the classes: Array, String and Symbol. Also it will add 'generate' method to Kernel
6
+ # aliases: 'gen' for 'generate' and 'val' for 'validate'
7
+ # Examples of use:
8
+ # "(,3:N,) ,3:N,-,2:N,-,2:N".split(",").generate #>(937) 980-65-05
9
+ # %w{( 3:N ) 1:_ 3:N - 2:N - 2:N}.gen #>(045) 448-63-09
10
+ # ["1:L", "5-10:LN", "-", "3:N"].gen #>zqWihV-746
11
+ # gen("10:N") #>3433409877
12
+ # "20-30:@".gen #>dkj34MljjJD-df@jfdluul.dfu
13
+ # "10:L/N/[/-./%d%]".validate("12ds6f--.s") #>[:value, :string_set_not_allowed]
14
+ # "20-40:@".validate(my_email)
15
+ # national_chars: (Array, default: english alphabet)
16
+ # Set of characters that will be used when using T pattern
17
+ # optimistic: (TrueFalse, default: true)
18
+ # If true it will check on the strings of the array positions if they have the pattern format and assume in that case that is a pattern.
19
+ # dont_repeat: (TrueFalse, default: false)
20
+ # If you want to generate for example 1000 strings and be sure all those strings are different you can set it to true
21
+ # default_infinite: (Integer, default: 10)
22
+ # In case using regular expressions the maximum when using * or + for repetitions
23
+ class StringPattern
24
+ class << self
25
+ attr_accessor :national_chars, :optimistic, :dont_repeat, :cache, :cache_values, :default_infinite
26
+ end
27
+ @national_chars = (('a'..'z').to_a + ('A'..'Z').to_a).join
28
+ @optimistic = true
29
+ @cache = Hash.new()
30
+ @cache_values = Hash.new()
31
+ @dont_repeat = false
32
+ @default_infinite = 10
33
+ NUMBER_SET = ('0'..'9').to_a
34
+ SPECIAL_SET = [' ', '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', '_', '+', '=', '{', '}', '[', ']', "'", ';', ':', '?', '>', '<', '`', '|', '/', '"']
35
+ ALPHA_SET_LOWER = ('a'..'z').to_a
36
+ ALPHA_SET_CAPITAL = ('A'..'Z').to_a
37
+ @palabras = []
38
+ @palabras_camel = []
39
+ @words = []
40
+ @words_camel = []
41
+ @palabras_short = []
42
+ @words_short = []
43
+ @palabras_camel_short = []
44
+ @words_camel_short = []
45
+
46
+ Pattern = Struct.new(:min_length, :max_length, :symbol_type, :required_data, :excluded_data, :data_provided,
47
+ :string_set, :all_characters_set, :unique)
48
+
49
+ ###############################################
50
+ # Analyze the pattern supplied and returns an object of Pattern structure including:
51
+ # min_length, max_length, symbol_type, required_data, excluded_data, data_provided, string_set, all_characters_set
52
+ ###############################################
53
+ def StringPattern.analyze(pattern, silent: false)
54
+ return @cache[pattern.to_s] unless @cache[pattern.to_s].nil?
55
+ min_length, max_length, symbol_type = pattern.to_s.scan(/(\d+)-(\d+):(.+)/)[0]
56
+ if min_length.nil?
57
+ min_length, symbol_type = pattern.to_s.scan(/^!?(\d+):(.+)/)[0]
58
+ max_length = min_length
59
+ if min_length.nil?
60
+ puts "pattern argument not valid on StringPattern.generate: #{pattern.inspect}" unless silent
61
+ return pattern.to_s
62
+ end
63
+ end
64
+ if symbol_type[-1]=="&"
65
+ symbol_type.chop!
66
+ unique = true
67
+ else
68
+ unique = false
69
+ end
70
+
71
+ symbol_type = '!' + symbol_type if pattern.to_s[0] == '!'
72
+ min_length = min_length.to_i
73
+ max_length = max_length.to_i
74
+
75
+ required_data = Array.new
76
+ excluded_data = Array.new
77
+ required = false
78
+ excluded = false
79
+ data_provided = Array.new
80
+ a = symbol_type
81
+ begin_provided = a.index('[')
82
+ excluded_end_tag = false
83
+ unless begin_provided.nil?
84
+ c = begin_provided + 1
85
+ until c == a.size or (a[c..c] == ']' and a[c..c + 1] != ']]')
86
+ if a[c..c + 1] == ']]'
87
+ data_provided.push(']')
88
+ c = c + 2
89
+ elsif a[c..c + 1] == '%%' and !excluded then
90
+ data_provided.push('%')
91
+ c = c + 2
92
+ else
93
+ if a[c..c] == '/' and !excluded
94
+ if a[c..c + 1] == '//'
95
+ data_provided.push(a[c..c])
96
+ if required
97
+ required_data.push([a[c..c]])
98
+ end
99
+ c = c + 1
100
+ else
101
+ if !required
102
+ required = true
103
+ else
104
+ required = false
105
+ end
106
+ end
107
+ else
108
+ if required
109
+ required_data.push([a[c..c]])
110
+ else
111
+ if a[c..c] == '%'
112
+ if a[c..c + 1] == '%%' and excluded
113
+ excluded_data.push([a[c..c]])
114
+ c = c + 1
115
+ else
116
+ if !excluded
117
+ excluded = true
118
+ else
119
+ excluded = false
120
+ excluded_end_tag = true
121
+ end
122
+ end
123
+ else
124
+ if excluded
125
+ excluded_data.push([a[c..c]])
126
+ end
127
+ end
128
+
129
+ end
130
+ if excluded == false and excluded_end_tag == false
131
+ data_provided.push(a[c..c])
132
+ end
133
+ excluded_end_tag = false
134
+ end
135
+ c = c + 1
136
+ end
137
+ end
138
+ symbol_type = symbol_type[0..begin_provided].to_s + symbol_type[c..symbol_type.size].to_s
139
+ end
140
+
141
+ required = false
142
+ required_symbol = ''
143
+ if symbol_type.include?("/")
144
+ symbol_type.chars.each {|stc|
145
+ if stc == '/'
146
+ if !required
147
+ required = true
148
+ else
149
+ required = false
150
+ end
151
+ else
152
+ if required
153
+ required_symbol += stc
154
+ end
155
+ end
156
+ }
157
+ end
158
+
159
+ national_set = @national_chars.chars
160
+
161
+ if symbol_type.include?('L') then
162
+ alpha_set = ALPHA_SET_LOWER + ALPHA_SET_CAPITAL
163
+ elsif symbol_type.include?('x')
164
+ alpha_set = ALPHA_SET_LOWER
165
+ if symbol_type.include?('X')
166
+ alpha_set = alpha_set + ALPHA_SET_CAPITAL
167
+ end
168
+ elsif symbol_type.include?('X')
169
+ alpha_set = ALPHA_SET_CAPITAL
170
+ else
171
+ alpha_set = []
172
+ end
173
+ if symbol_type.include?('T')
174
+ alpha_set = alpha_set + national_set
175
+ end
176
+
177
+ unless required_symbol.nil?
178
+ if required_symbol.include?('x')
179
+ required_data.push ALPHA_SET_LOWER
180
+ end
181
+ if required_symbol.include?('X')
182
+ required_data.push ALPHA_SET_CAPITAL
183
+ end
184
+ if required_symbol.include?('L')
185
+ required_data.push(ALPHA_SET_CAPITAL + ALPHA_SET_LOWER)
186
+ end
187
+ if required_symbol.include?('T')
188
+ required_data.push national_set
189
+ end
190
+ required_symbol = required_symbol.downcase
191
+ end
192
+ string_set = Array.new
193
+ all_characters_set = ALPHA_SET_CAPITAL + ALPHA_SET_LOWER + NUMBER_SET + SPECIAL_SET + data_provided + national_set
194
+
195
+ if symbol_type.include?('_')
196
+ unless symbol_type.include?('$')
197
+ string_set.push(' ')
198
+ end
199
+ if required_symbol.include?('_')
200
+ required_data.push([' '])
201
+ end
202
+ end
203
+
204
+ #symbol_type = symbol_type.downcase
205
+
206
+ if symbol_type.downcase.include?('x') or symbol_type.downcase.include?('l') or symbol_type.downcase.include?('t')
207
+ string_set = string_set + alpha_set
208
+ end
209
+ if symbol_type.downcase.include?('n')
210
+ string_set = string_set + NUMBER_SET
211
+ end
212
+ if symbol_type.include?('$')
213
+ string_set = string_set + SPECIAL_SET
214
+ end
215
+ if symbol_type.include?('*')
216
+ string_set = string_set + all_characters_set
217
+ end
218
+ if data_provided.size != 0
219
+ string_set = string_set + data_provided
220
+ end
221
+ unless required_symbol.empty?
222
+ if required_symbol.include?('n')
223
+ required_data.push NUMBER_SET
224
+ end
225
+ if required_symbol.include?('$')
226
+ required_data.push SPECIAL_SET
227
+ end
228
+ end
229
+ unless excluded_data.empty?
230
+ string_set = string_set - excluded_data.flatten
231
+ end
232
+ string_set.uniq!
233
+ @cache[pattern.to_s] = Pattern.new(min_length, max_length, symbol_type, required_data, excluded_data, data_provided,
234
+ string_set, all_characters_set, unique)
235
+ return @cache[pattern.to_s]
236
+ end
237
+
238
+ ###############################################
239
+ # Generate a random string based on the pattern supplied
240
+ # (if SP_ADD_TO_RUBY==true, by default is true) To simplify its use it is part of the String, Array, Symbol and Kernel Ruby so can be easily used also like this:
241
+ # "10-15:Ln/x/".generate #generate method on String class (alias: gen)
242
+ # ['(', :'3:N', ')', :'6-8:N'].generate #generate method on Array class (alias: gen)
243
+ # generate("10-15:Ln/x/") #generate Ruby Kernel method
244
+ # generate(['(', :'3:N', ')', :'6-8:N']) #generate Ruby Kernel method
245
+ # "(,3:N,) ,3:N,-,2:N,-,2:N".split(",").generate #>(937) #generate method on Array class (alias: gen)
246
+ # %w{( 3:N ) 1:_ 3:N - 2:N - 2:N}.gen #generate method on Array class, using alias gen method
247
+ # Input:
248
+ # pattern: array or string of different patterns. A pattern is a string with this info:
249
+ # "length:symbol_type" or "min_length-max_length:symbol_type"
250
+ # In case an array supplied, the positions using a string pattern should be supplied as symbols if StringPattern.optimistic==false
251
+ #
252
+ # These are the possible string patterns you will be able to supply:
253
+ # If at the beginning we supply the character ! the resulting string won't fulfill the pattern. This character need to be the first character of the pattern.
254
+ # min_length -- minimum length of the string
255
+ # max_length (optional) -- maximum length of the string. If not provided the result will be with the min_length provided
256
+ # symbol_type -- the type of the string we want.
257
+ # you can use a combination of any ot these:
258
+ # x for alpha in lowercase
259
+ # X for alpha in capital letters
260
+ # L for all kind of alpha in capital and lower letters
261
+ # T for the national characters defined on StringPattern.national_chars
262
+ # n for number
263
+ # $ for special characters (includes space)
264
+ # _ for space
265
+ # * all characters
266
+ # [characters] the characters we want. If we want to add also the ] character you have to write: ]]. If we want to add also the % character you have to write: %%
267
+ # %characters% the characters we don't want on the resulting string. %% to exclude the character %
268
+ # /symbols or characters/ If we want these characters to be included on the resulting string. If we want to add also the / character you have to write: //
269
+ # We can supply 0 to allow empty strings, this character need to be at the beginning
270
+ # If you want to include the character " use \"
271
+ # If you want to include the character \ use \\
272
+ # If you want to include the character [ use \[
273
+ # Other uses:
274
+ # @ for email
275
+ # W for English words, capital and lower
276
+ # w for English words only lower and words separated by underscore
277
+ # P for Spanish words, capital and lower
278
+ # p for Spanish words only lower and words separated by underscore
279
+ # Examples:
280
+ # [:"6:X", :"3-8:_N"]
281
+ # # it will return a string starting with 6 capital letters and then a string containing numbers and space from 3 to 8 characters, for example: "LDJKKD34 555"
282
+ # [:"6-15:L_N", "fixed text", :"3:N"]
283
+ # # it will return a string of 6-15 characters containing Letters-spaces-numbers, then the text: 'fixed text' and at the end a string of 3 characters containing numbers, for example: ["L_N",6,15],"fixed text",["N",3] "3 Am399 afixed text882"
284
+ # "10-20:LN[=#]"
285
+ # # it will return a string of 10-20 characters containing Letters and/or numbers and/or the characters = and #, for example: eiyweQFWeL#do4Vl
286
+ # "30:TN_[#=]/x/"
287
+ # # it will return a string of 30 characters containing national characters defined on StringPattern.national_chars and/or numbers and/or spaces and/or the characters # = and it is necessary the resultant string includes lower alpha chars. For example: HaEdQTzJ3=OtXMh1mAPqv7NCy=upLy
288
+ # "10:N[%0%]"
289
+ # # 10 characters length containing numbers and excluding the character 0, for example: 3523497757
290
+ # "10:N[%0%/AB/]"
291
+ # # 10 characters length containing numbers and excluding the character 0 and necessary to contain the characters A B, for example: 3AA4AA57BB
292
+ # "!10:N[%0%/AB/]"
293
+ # # it will generate a string that doesn't fulfill the pattern supplied, examples:
294
+ # # a6oMQ4JK9g
295
+ # # /Y<N6Aa[ae
296
+ # # 3444439A34B32
297
+ # "10:N[%0%/AB/]", errors: [:length]
298
+ # # it will generate a string following the pattern and with the errors supplied, in this case, length, example: AB44
299
+ # Output:
300
+ # the generated string
301
+ ###############################################
302
+ def StringPattern.generate(pattern, expected_errors: [], **synonyms)
303
+ tries = 0
304
+ begin
305
+ good_result = true
306
+ tries += 1
307
+ string = ''
308
+
309
+ expected_errors = synonyms[:errors] if synonyms.keys.include?(:errors)
310
+
311
+ if expected_errors.kind_of?(Symbol)
312
+ expected_errors = [expected_errors]
313
+ end
314
+
315
+ if pattern.kind_of?(Array)
316
+ pattern.each {|pat|
317
+
318
+ if pat.kind_of?(Array) # for the case it is one of the values
319
+ pat = pat.sample
320
+ end
321
+
322
+ if pat.kind_of?(Symbol)
323
+ if pat.to_s.scan(/^!?\d+-?\d*:.+/).size > 0
324
+ string << StringPattern.generate(pat.to_s, expected_errors: expected_errors)
325
+ else
326
+ string << pat.to_s
327
+ end
328
+ elsif pat.kind_of?(String) then
329
+ if @optimistic and pat.to_s.scan(/^!?\d+-?\d*:.+/).size > 0
330
+ string << StringPattern.generate(pat.to_s, expected_errors: expected_errors)
331
+ else
332
+ string << pat
333
+ end
334
+ else
335
+ puts "StringPattern.generate: it seems you supplied wrong array of patterns: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
336
+ return ''
337
+ end
338
+ }
339
+ return string
340
+ elsif pattern.kind_of?(String) or pattern.kind_of?(Symbol)
341
+ patt = StringPattern.analyze(pattern)
342
+ min_length = patt.min_length
343
+ max_length = patt.max_length
344
+ symbol_type = patt.symbol_type
345
+
346
+ required_data = patt.required_data
347
+ excluded_data = patt.excluded_data
348
+ string_set = patt.string_set
349
+ all_characters_set = patt.all_characters_set
350
+
351
+ required_chars = Array.new
352
+ unless required_data.size == 0
353
+ required_data.each {|rd|
354
+ required_chars << rd if rd.size == 1
355
+ }
356
+ unless excluded_data.size == 0
357
+ if (required_chars.flatten & excluded_data.flatten).size > 0
358
+ puts "pattern argument not valid on StringPattern.generate, a character cannot be required and excluded at the same time: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
359
+ return ''
360
+ end
361
+ end
362
+ end
363
+ string_set_not_allowed = Array.new
364
+ elsif pattern.kind_of?(Regexp)
365
+ return generate(pattern.to_sp, expected_errors: expected_errors)
366
+ else
367
+ puts "pattern argument not valid on StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
368
+ return pattern.to_s
369
+ end
370
+
371
+
372
+ allow_empty = false
373
+ deny_pattern = false
374
+ if symbol_type[0..0] == '!'
375
+ deny_pattern = true
376
+ possible_errors = [:length, :value, :required_data, :excluded_data, :string_set_not_allowed]
377
+ (rand(possible_errors.size) + 1).times {
378
+ expected_errors << possible_errors.sample
379
+ }
380
+ expected_errors.uniq!
381
+ if symbol_type[1..1] == '0'
382
+ allow_empty = true
383
+ end
384
+ elsif symbol_type[0..0] == '0' then
385
+ allow_empty = true
386
+ end
387
+
388
+ if expected_errors.include?(:min_length) or expected_errors.include?(:length) or
389
+ expected_errors.include?(:max_length)
390
+ allow_empty = !allow_empty
391
+ elsif expected_errors.include?(:value) or
392
+ expected_errors.include?(:excluded_data) or
393
+ expected_errors.include?(:required_data) or
394
+ expected_errors.include?(:string_set_not_allowed) and allow_empty
395
+ allow_empty = false
396
+ end
397
+
398
+ length = min_length
399
+ symbol_type_orig = symbol_type
400
+
401
+ expected_errors_left = expected_errors.dup
402
+
403
+ symbol_type = symbol_type_orig
404
+
405
+ unless deny_pattern
406
+ if required_data.size == 0 and expected_errors_left.include?(:required_data)
407
+ puts "required data not supplied on pattern so it won't be possible to generate a wrong string. StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
408
+ return ''
409
+ end
410
+
411
+ if excluded_data.size == 0 and expected_errors_left.include?(:excluded_data)
412
+ puts "excluded data not supplied on pattern so it won't be possible to generate a wrong string. StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
413
+ return ''
414
+ end
415
+
416
+ if expected_errors_left.include?(:string_set_not_allowed)
417
+ string_set_not_allowed = all_characters_set - string_set
418
+ if string_set_not_allowed.size == 0 then
419
+ puts "all characters are allowed so it won't be possible to generate a wrong string. StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
420
+ return ''
421
+ end
422
+ end
423
+ end
424
+
425
+ if expected_errors_left.include?(:min_length) or
426
+ expected_errors_left.include?(:max_length) or
427
+ expected_errors_left.include?(:length)
428
+ if expected_errors_left.include?(:min_length) or
429
+ (min_length > 0 and expected_errors_left.include?(:length) and rand(2) == 0)
430
+ if min_length > 0
431
+ if allow_empty
432
+ length = rand(min_length).to_i
433
+ else
434
+ length = rand(min_length - 1).to_i + 1
435
+ end
436
+ if required_data.size > length and required_data.size < min_length
437
+ length = required_data.size
438
+ end
439
+ expected_errors_left.delete(:length)
440
+ expected_errors_left.delete(:min_length)
441
+ else
442
+ puts "min_length is 0 so it won't be possible to generate a wrong string smaller than 0 characters. StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
443
+ return ''
444
+ end
445
+ elsif expected_errors_left.include?(:max_length) or expected_errors_left.include?(:length)
446
+ length = max_length + 1 + rand(max_length).to_i
447
+ expected_errors_left.delete(:length)
448
+ expected_errors_left.delete(:max_length)
449
+ end
450
+ else
451
+ if allow_empty and rand(7) == 1
452
+ length = 0
453
+ else
454
+ if max_length == min_length
455
+ length = min_length
456
+ else
457
+ length = min_length + rand(max_length - min_length + 1)
458
+ end
459
+ end
460
+ end
461
+
462
+ if deny_pattern
463
+ if required_data.size == 0 and expected_errors_left.include?(:required_data)
464
+ expected_errors_left.delete(:required_data)
465
+ end
466
+
467
+ if excluded_data.size == 0 and expected_errors_left.include?(:excluded_data)
468
+ expected_errors_left.delete(:excluded_data)
469
+ end
470
+
471
+ if expected_errors_left.include?(:string_set_not_allowed)
472
+ string_set_not_allowed = all_characters_set - string_set
473
+ if string_set_not_allowed.size == 0
474
+ expected_errors_left.delete(:string_set_not_allowed)
475
+ end
476
+ end
477
+
478
+ if symbol_type == '!@' and expected_errors_left.size == 0 and !expected_errors.include?(:length) and
479
+ (expected_errors.include?(:required_data) or expected_errors.include?(:excluded_data))
480
+ expected_errors_left.push(:value)
481
+ end
482
+
483
+ end
484
+
485
+ string = ''
486
+ if symbol_type != '@' and symbol_type != '!@' and length != 0 and string_set.size != 0
487
+ if string_set.size != 0
488
+ 1.upto(length) {|i| string << string_set.sample.to_s
489
+ }
490
+ end
491
+ if required_data.size > 0
492
+ positions_to_set = (0..(string.size - 1)).to_a
493
+ required_data.each {|rd|
494
+ if (string.chars & rd).size > 0
495
+ rd_to_set = (string.chars & rd).sample
496
+ else
497
+ rd_to_set = rd.sample
498
+ end
499
+ if ((0...string.length).find_all {|i| string[i, 1] == rd_to_set}).size == 0
500
+ if positions_to_set.size == 0
501
+ puts "pattern not valid on StringPattern.generate, not possible to generate a valid string: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
502
+ return ''
503
+ else
504
+ k = positions_to_set.sample
505
+ string[k] = rd_to_set
506
+ positions_to_set.delete(k)
507
+ end
508
+ else
509
+ k = ((0...string.length).find_all {|i| string[i, 1] == rd_to_set}).sample
510
+ positions_to_set.delete(k)
511
+ end
512
+ }
513
+ end
514
+ excluded_data.each {|ed|
515
+ if (string.chars & ed).size > 0
516
+ (string.chars & ed).each {|s|
517
+ string.gsub!(s, string_set.sample)
518
+ }
519
+ end
520
+ }
521
+
522
+ if expected_errors_left.include?(:value)
523
+ string_set_not_allowed = all_characters_set - string_set if string_set_not_allowed.size == 0
524
+ if string_set_not_allowed.size == 0
525
+ puts "Not possible to generate a non valid string on StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
526
+ return ''
527
+ end
528
+ (rand(string.size) + 1).times {
529
+ string[rand(string.size)] = (all_characters_set - string_set).sample
530
+ }
531
+ expected_errors_left.delete(:value)
532
+ end
533
+
534
+ if expected_errors_left.include?(:required_data) and required_data.size > 0
535
+ (rand(required_data.size) + 1).times {
536
+ chars_to_remove = required_data.sample
537
+ chars_to_remove.each {|char_to_remove|
538
+ string.gsub!(char_to_remove, (string_set - chars_to_remove).sample)
539
+ }
540
+ }
541
+ expected_errors_left.delete(:required_data)
542
+ end
543
+
544
+ if expected_errors_left.include?(:excluded_data) and excluded_data.size > 0
545
+ (rand(string.size) + 1).times {
546
+ string[rand(string.size)] = excluded_data.sample.sample
547
+ }
548
+ expected_errors_left.delete(:excluded_data)
549
+ end
550
+
551
+ if expected_errors_left.include?(:string_set_not_allowed)
552
+ string_set_not_allowed = all_characters_set - string_set if string_set_not_allowed.size == 0
553
+ if string_set_not_allowed.size > 0
554
+ (rand(string.size) + 1).times {
555
+ string[rand(string.size)] = string_set_not_allowed.sample
556
+ }
557
+ expected_errors_left.delete(:string_set_not_allowed)
558
+ end
559
+ end
560
+ elsif (symbol_type == 'W' or symbol_type == 'P' or symbol_type=='w' or symbol_type =='p') and length>0
561
+ words = []
562
+ words_short = []
563
+ if symbol_type == 'W'
564
+ if @words_camel.empty?
565
+ require "pathname"
566
+ require "json"
567
+ filename = File.join Pathname(File.dirname(__FILE__)), "../data", "english/nouns.json"
568
+ nouns = JSON.parse(File.read(filename))
569
+ filename = File.join Pathname(File.dirname(__FILE__)), "../data", "english/adjs.json"
570
+ adjs = JSON.parse(File.read(filename))
571
+ nouns = nouns.map(&:to_camel_case)
572
+ adjs = adjs.map(&:to_camel_case)
573
+ @words_camel = adjs + nouns
574
+ @words_camel_short = @words_camel.sample(1000)
575
+ end
576
+ words = @words_camel
577
+ words_short = @words_camel_short
578
+ elsif symbol_type == 'w'
579
+ if @words.empty?
580
+ require "pathname"
581
+ require "json"
582
+ filename = File.join Pathname(File.dirname(__FILE__)), "../data", "english/nouns.json"
583
+ nouns = JSON.parse(File.read(filename))
584
+ filename = File.join Pathname(File.dirname(__FILE__)), "../data", "english/adjs.json"
585
+ adjs = JSON.parse(File.read(filename))
586
+ @words = adjs + nouns
587
+ @words_short = @words.sample(1000)
588
+ end
589
+ words = @words
590
+ words_short = @words_short
591
+ elsif symbol_type == 'P'
592
+ if @palabras_camel.empty?
593
+ require "pathname"
594
+ require "json"
595
+ filename = File.join Pathname(File.dirname(__FILE__)), "../data", "spanish/palabras.json"
596
+ palabras = JSON.parse(File.read(filename))
597
+ palabras = palabras.map(&:to_camel_case)
598
+ @palabras_camel = palabras
599
+ @palabras_camel_short = @palabras_camel.sample(1000)
600
+ end
601
+ words = @palabras_camel
602
+ words_short = @palabras_camel_short
603
+ elsif symbol_type == 'p'
604
+ if @palabras.empty?
605
+ require "pathname"
606
+ require "json"
607
+ filename = File.join Pathname(File.dirname(__FILE__)), "../data", "spanish/palabras.json"
608
+ palabras = JSON.parse(File.read(filename))
609
+ @palabras = palabras
610
+ @palabras_short = @palabras.sample(1000)
611
+ end
612
+ words = @palabras
613
+ words_short = @palabras_short
614
+ end
615
+
616
+ wordr = ""
617
+ wordr_array = []
618
+ tries = 0
619
+ while wordr.length < min_length
620
+ tries += 1
621
+ length = rand(max_length - wordr.length)+1
622
+ if (tries % 100) == 0
623
+ words_short = words.sample(1000)
624
+ end
625
+ if tries > 1000
626
+ wordr += 'A'*length
627
+ break
628
+ end
629
+ if symbol_type == 'w' or symbol_type == "p"
630
+ res = (words_short.select { |word| word.length == length }).sample.to_s
631
+ unless res.to_s==''
632
+ wordr_array<<res
633
+ wordr = wordr_array.join("_")
634
+ end
635
+ else
636
+ wordr += (words_short.select { |word| word.length == length }).sample.to_s
637
+ end
638
+ end
639
+ good_result = true
640
+ string = wordr
641
+
642
+ elsif (symbol_type == '@' or symbol_type == '!@') and length > 0
643
+ if min_length > 6 and length < 6
644
+ length = 6
645
+ end
646
+ if deny_pattern and
647
+ (expected_errors.include?(:required_data) or expected_errors.include?(:excluded_data) or
648
+ expected_errors.include?(:string_set_not_allowed))
649
+ expected_errors_left.push(:value)
650
+ expected_errors.push(:value)
651
+ expected_errors.uniq!
652
+ expected_errors_left.uniq!
653
+ end
654
+
655
+ expected_errors_left_orig = expected_errors_left.dup
656
+ tries = 0
657
+ begin
658
+ expected_errors_left = expected_errors_left_orig.dup
659
+ tries += 1
660
+ string = ''
661
+ alpha_set = ALPHA_SET_LOWER + ALPHA_SET_CAPITAL
662
+ string_set = alpha_set + NUMBER_SET + ['.'] + ['_'] + ['-']
663
+ string_set_not_allowed = all_characters_set - string_set
664
+
665
+ extension = '.'
666
+ at_sign = '@'
667
+
668
+ if expected_errors_left.include?(:value)
669
+ if rand(2) == 1
670
+ extension = (all_characters_set - ['.']).sample
671
+ expected_errors_left.delete(:value)
672
+ expected_errors_left.delete(:required_data)
673
+ end
674
+ if rand(2) == 1
675
+ 1.upto(rand(7)) {|i| extension << alpha_set.sample.downcase
676
+ }
677
+ (rand(extension.size) + 1).times {
678
+ extension[rand(extension.size)] = (string_set - alpha_set - ['.']).sample
679
+ }
680
+ expected_errors_left.delete(:value)
681
+ else
682
+ 1.upto(rand(3) + 2) {|i| extension << alpha_set.sample.downcase
683
+ }
684
+ end
685
+ if rand(2) == 1
686
+ at_sign = (string_set - ['@']).sample
687
+ expected_errors_left.delete(:value)
688
+ expected_errors_left.delete(:required_data)
689
+ end
690
+ else
691
+ if length > 6
692
+ 1.upto(rand(3) + 2) {|i| extension << alpha_set.sample.downcase
693
+ }
694
+ else
695
+ 1.upto(2) {|i| extension << alpha_set.sample.downcase
696
+ }
697
+ end
698
+ end
699
+ length_e = length - extension.size - 1
700
+ length1 = rand(length_e - 1) + 1
701
+ length2 = length_e - length1
702
+ 1.upto(length1) {|i| string << string_set.sample}
703
+
704
+ string << at_sign
705
+
706
+ domain = ''
707
+ domain_set = alpha_set + NUMBER_SET + ['.'] + ['-']
708
+ 1.upto(length2) {|i| domain << domain_set.sample.downcase
709
+ }
710
+
711
+ if expected_errors.include?(:value) and rand(2) == 1 and domain.size > 0
712
+ (rand(domain.size) + 1).times {
713
+ domain[rand(domain.size)] = (all_characters_set - domain_set).sample
714
+ }
715
+ expected_errors_left.delete(:value)
716
+ end
717
+ string << domain << extension
718
+
719
+ if expected_errors_left.include?(:value) or expected_errors_left.include?(:string_set_not_allowed)
720
+ (rand(string.size) + 1).times {
721
+ string[rand(string.size)] = string_set_not_allowed.sample
722
+ }
723
+ expected_errors_left.delete(:value)
724
+ expected_errors_left.delete(:string_set_not_allowed)
725
+ end
726
+
727
+ error_regular_expression = false
728
+
729
+ if deny_pattern and expected_errors.include?(:length)
730
+ good_result = true #it is already with wrong length
731
+ else
732
+ # I'm doing this because many times the regular expression checking hangs with these characters
733
+ wrong = %w(.. __ -- ._ _. .- -. _- -_ @. @_ @- .@ _@ -@ @@)
734
+ if !(Regexp.union(*wrong) === string) #don't include any or the wrong strings
735
+ if string.index('@').to_i > 0 and
736
+ string[0..(string.index('@') - 1)].scan(/([a-z0-9]+([\+\._\-][a-z0-9]|)*)/i).join == string[0..(string.index('@') - 1)] and
737
+ string[(string.index('@') + 1)..-1].scan(/([0-9a-z]+([\.-][a-z0-9]|)*)/i).join == string[string[(string.index('@') + 1)..-1]]
738
+ error_regular_expression = false
739
+ else
740
+ error_regular_expression = true
741
+ end
742
+ else
743
+ error_regular_expression = true
744
+ end
745
+
746
+ if expected_errors.size == 0
747
+ if error_regular_expression
748
+ good_result = false
749
+ else
750
+ good_result = true
751
+ end
752
+ elsif expected_errors_left.size == 0 and
753
+ (expected_errors - [:length, :min_length, :max_length]).size == 0
754
+ good_result = true
755
+ elsif expected_errors != [:length]
756
+ if !error_regular_expression
757
+ good_result = false
758
+ elsif expected_errors.include?(:value)
759
+ good_result = true
760
+ end
761
+ end
762
+ end
763
+
764
+ end until good_result or tries > 100
765
+ unless good_result
766
+ puts "Not possible to generate an email on StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
767
+ return ''
768
+ end
769
+ end
770
+ if @dont_repeat
771
+ if @cache_values[pattern.to_s].nil?
772
+ @cache_values[pattern.to_s] = Array.new()
773
+ @cache_values[pattern.to_s].push(string)
774
+ good_result = true
775
+ elsif @cache_values[pattern.to_s].include?(string)
776
+ good_result = false
777
+ else
778
+ @cache_values[pattern.to_s].push(string)
779
+ good_result = true
780
+ end
781
+ end
782
+ if pattern.kind_of?(Symbol) and patt.unique
783
+ if @cache_values[pattern.__id__].nil?
784
+ @cache_values[pattern.__id__] = Array.new()
785
+ @cache_values[pattern.__id__].push(string)
786
+ good_result = true
787
+ elsif @cache_values[pattern.__id__].include?(string)
788
+ good_result = false
789
+ else
790
+ @cache_values[pattern.__id__].push(string)
791
+ good_result = true
792
+ end
793
+ end
794
+ end until good_result or tries > 10000
795
+ unless good_result
796
+ puts "Not possible to generate the string on StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
797
+ puts "Take in consideration if you are using StringPattern.dont_repeat=true that you don't try to generate more strings that are possible to be generated"
798
+ return ''
799
+ end
800
+
801
+ return string
802
+ end
803
+
804
+
805
+ ##############################################
806
+ # This method is defined to validate if the text_to_validate supplied follows the pattern
807
+ # It works also with array of patterns but in that case will return only true or false
808
+ # input:
809
+ # text (String) (synonyms: text_to_validate, validate) -- The text to validate
810
+ # pattern -- symbol with this info: "length:symbol_type" or "min_length-max_length:symbol_type"
811
+ # min_length -- minimum length of the string
812
+ # max_length (optional) -- maximum length of the string. If not provided the result will be with the min_length provided
813
+ # symbol_type -- the type of the string we want.
814
+ # expected_errors (Array of symbols) (optional) (synonyms: errors) -- :length, :min_length, :max_length, :value, :required_data, :excluded_data, :string_set_not_allowed
815
+ # not_expected_errors (Array of symbols) (optional) (synonyms: not_errors, non_expected_errors) -- :length, :min_length, :max_length, :value, :required_data, :excluded_data, :string_set_not_allowed
816
+ # example:
817
+ # validate(text: "This text will be validated", pattern: :"10-20:Xn", expected_errors: [:value, :max_length])
818
+ #
819
+ # Output:
820
+ # if expected_errors and not_expected_errors are not supplied: an array with all detected errors
821
+ # if expected_errors or not_expected_errors supplied: true or false
822
+ # if array of patterns supplied, it will return true or false
823
+ ###############################################
824
+ def StringPattern.validate(text: '', pattern: '', expected_errors: [], not_expected_errors: [], **synonyms)
825
+ text_to_validate = text
826
+ text_to_validate = synonyms[:text_to_validate] if synonyms.keys.include?(:text_to_validate)
827
+ text_to_validate = synonyms[:validate] if synonyms.keys.include?(:validate)
828
+ expected_errors = synonyms[:errors] if synonyms.keys.include?(:errors)
829
+ not_expected_errors = synonyms[:not_errors] if synonyms.keys.include?(:not_errors)
830
+ not_expected_errors = synonyms[:non_expected_errors] if synonyms.keys.include?(:non_expected_errors)
831
+ #:length, :min_length, :max_length, :value, :required_data, :excluded_data, :string_set_not_allowed
832
+ if (expected_errors.include?(:min_length) or expected_errors.include?(:max_length)) and !expected_errors.include?(:length)
833
+ expected_errors.push(:length)
834
+ end
835
+ if (not_expected_errors.include?(:min_length) or not_expected_errors.include?(:max_length)) and !not_expected_errors.include?(:length)
836
+ not_expected_errors.push(:length)
837
+ end
838
+ if pattern.kind_of?(Array) and pattern.size == 1
839
+ pattern = pattern[0]
840
+ elsif pattern.kind_of?(Array) and pattern.size > 1 then
841
+ total_min_length = 0
842
+ total_max_length = 0
843
+ all_errors_collected = Array.new
844
+ result = true
845
+ num_patt = 0
846
+ patterns = Array.new
847
+ pattern.each {|pat|
848
+ if (pat.kind_of?(String) and (!StringPattern.optimistic or
849
+ (StringPattern.optimistic and pat.to_s.scan(/(\d+)-(\d+):(.+)/).size == 0 and pat.to_s.scan(/^!?(\d+):(.+)/).size == 0))) #fixed text
850
+ symbol_type = ''
851
+ min_length = max_length = pat.length
852
+ elsif pat.kind_of?(Symbol) or (pat.kind_of?(String) and StringPattern.optimistic and
853
+ (pat.to_s.scan(/(\d+)-(\d+):(.+)/).size > 0 or pat.to_s.scan(/^!?(\d+):(.+)/).size > 0))
854
+ patt = StringPattern.analyze(pat)
855
+ min_length = patt.min_length
856
+ max_length = patt.max_length
857
+ symbol_type = patt.symbol_type
858
+ else
859
+ puts "String pattern class not supported (#{pat.class} for #{pat})"
860
+ end
861
+
862
+ patterns.push({pattern: pat, min_length: min_length, max_length: max_length, symbol_type: symbol_type})
863
+
864
+ total_min_length += min_length
865
+ total_max_length += max_length
866
+
867
+ if num_patt == (pattern.size - 1) # i am in the last one
868
+ if text_to_validate.length < total_min_length
869
+ all_errors_collected.push(:length)
870
+ all_errors_collected.push(:min_length)
871
+ end
872
+
873
+ if text_to_validate.length > total_max_length
874
+ all_errors_collected.push(:length)
875
+ all_errors_collected.push(:max_length)
876
+ end
877
+
878
+ end
879
+ num_patt += 1
880
+
881
+
882
+ }
883
+
884
+ num_patt = 0
885
+ patterns.each {|patt|
886
+
887
+ tmp_result = false
888
+ (patt[:min_length]..patt[:max_length]).each {|n|
889
+ res = StringPattern.validate(text: text_to_validate[0..n - 1], pattern: patt[:pattern], not_expected_errors: not_expected_errors)
890
+ if res.kind_of?(Array)
891
+ all_errors_collected += res
892
+ end
893
+
894
+ if res.kind_of?(TrueClass) or (res.kind_of?(Array) and res.size == 0) #valid
895
+ #we pass in the next one the rest of the pattern array list: pattern: pattern[num_patt+1..pattern.size]
896
+ res = StringPattern.validate(text: text_to_validate[n..text_to_validate.length], pattern: pattern[num_patt + 1..pattern.size], expected_errors: expected_errors, not_expected_errors: not_expected_errors)
897
+
898
+ if res.kind_of?(Array)
899
+ if ((all_errors_collected + res) - expected_errors).size > 0
900
+ tmp_result = false
901
+ else
902
+ all_errors_collected += res
903
+ tmp_result = true
904
+ end
905
+ elsif res.kind_of?(TrueClass) then
906
+ tmp_result = true
907
+ end
908
+ return true if tmp_result
909
+ end
910
+ }
911
+
912
+ unless tmp_result
913
+ return false
914
+ end
915
+ num_patt += 1
916
+ }
917
+ return result
918
+ end
919
+
920
+ if (pattern.kind_of?(String) and (!StringPattern.optimistic or
921
+ (StringPattern.optimistic and pattern.to_s.scan(/(\d+)-(\d+):(.+)/).size == 0 and pattern.to_s.scan(/^!?(\d+):(.+)/).size == 0))) #fixed text
922
+ symbol_type = ''
923
+ min_length = max_length = pattern.length
924
+ else #symbol
925
+ patt = StringPattern.analyze(pattern)
926
+ min_length = patt.min_length
927
+ max_length = patt.max_length
928
+ symbol_type = patt.symbol_type
929
+
930
+ required_data = patt.required_data
931
+ excluded_data = patt.excluded_data
932
+ string_set = patt.string_set
933
+ all_characters_set = patt.all_characters_set
934
+
935
+ required_chars = Array.new
936
+ required_data.each {|rd|
937
+ required_chars << rd if rd.size == 1
938
+ }
939
+ if (required_chars.flatten & excluded_data.flatten).size > 0
940
+ puts "pattern argument not valid on StringPattern.validate, a character cannot be required and excluded at the same time: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
941
+ return ''
942
+ end
943
+
944
+ end
945
+
946
+ if text_to_validate.nil?
947
+ return false
948
+ end
949
+ detected_errors = Array.new
950
+
951
+ if text_to_validate.length < min_length
952
+ detected_errors.push(:min_length)
953
+ detected_errors.push(:length)
954
+ end
955
+ if text_to_validate.length > max_length
956
+ detected_errors.push(:max_length)
957
+ detected_errors.push(:length)
958
+ end
959
+
960
+ if symbol_type == '' #fixed text
961
+ if pattern.to_s != text.to_s #not equal
962
+ detected_errors.push(:value)
963
+ detected_errors.push(:required_data)
964
+ end
965
+ else # pattern supplied
966
+ if symbol_type != '@'
967
+ if required_data.size > 0
968
+ required_data.each {|rd|
969
+ if (text_to_validate.chars & rd).size == 0
970
+ detected_errors.push(:value)
971
+ detected_errors.push(:required_data)
972
+ break
973
+ end
974
+ }
975
+ end
976
+ if excluded_data.size > 0
977
+ if (excluded_data & text_to_validate.chars).size > 0
978
+ detected_errors.push(:value)
979
+ detected_errors.push(:excluded_data)
980
+ end
981
+ end
982
+ string_set_not_allowed = all_characters_set - string_set
983
+ text_to_validate.chars.each {|st|
984
+ if string_set_not_allowed.include?(st)
985
+ detected_errors.push(:value)
986
+ detected_errors.push(:string_set_not_allowed)
987
+ break
988
+ end
989
+ }
990
+ else #symbol_type=="@"
991
+ string = text_to_validate
992
+ wrong = %w(.. __ -- ._ _. .- -. _- -_ @. @_ @- .@ _@ -@ @@)
993
+ if !(Regexp.union(*wrong) === string) #don't include any or the wrong strings
994
+ if string.index('@').to_i > 0 and
995
+ string[0..(string.index('@') - 1)].scan(/([a-z0-9]+([\+\._\-][a-z0-9]|)*)/i).join == string[0..(string.index('@') - 1)] and
996
+ string[(string.index('@') + 1)..-1].scan(/([0-9a-z]+([\.-][a-z0-9]|)*)/i).join == string[string[(string.index('@') + 1)..-1]]
997
+ error_regular_expression = false
998
+ else
999
+ error_regular_expression = true
1000
+ end
1001
+ else
1002
+ error_regular_expression = true
1003
+ end
1004
+
1005
+ if error_regular_expression
1006
+ detected_errors.push(:value)
1007
+ end
1008
+
1009
+ end
1010
+ end
1011
+
1012
+ if expected_errors.size == 0 and not_expected_errors.size == 0
1013
+ return detected_errors
1014
+ else
1015
+ if expected_errors & detected_errors == expected_errors
1016
+ if (not_expected_errors & detected_errors).size > 0
1017
+ return false
1018
+ else
1019
+ return true
1020
+ end
1021
+ else
1022
+ return false
1023
+ end
1024
+ end
1025
+ end
1026
+
1027
+ end
1028
+