Class: StringPattern

Inherits:
Object
  • Object
show all
Defined in:
lib/string_pattern.rb

Overview

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. If true it will add 'generate' and 'validate' methods to the classes: Array, String and Symbol. Also it will add 'generate' method to Kernel aliases: 'gen' for 'generate' and 'val' for 'validate' Examples of use: "(,3:N,) ,3:N,-,2:N,-,2:N".split(",").generate #>(937) 980-65-05 %w3:N ) 1:_ 3:N - 2:N - 2:N.gen #>(045) 448-63-09 ["1:L", "5-10:LN", "-", "3:N"].gen #>zqWihV-746 gen("10:N") #>3433409877 "20-30:@".gen #>[email protected] "10:L/N/[/-./%d%]".validate("12ds6f--.s") #>[:value, :string_set_not_allowed] "20-40:@".validate(my_email) national_chars: (Array, default: english alphabet) Set of characters that will be used when using T pattern optimistic: (TrueFalse, default: true) 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. dont_repeat: (TrueFalse, default: false) If you want to generate for example 1000 strings and be sure all those strings are different you can set it to true

Defined Under Namespace

Classes: Pattern

Constant Summary collapse

NUMBER_SET =
('0'..'9').to_a
SPECIAL_SET =
[' ', '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', '_', '+', '=', '{', '}', '[', ']', "'", ';', ':', '?', '>', '<', '`', '|', '/', '"']
ALPHA_SET_LOWER =
('a'..'z').to_a
ALPHA_SET_CAPITAL =
('A'..'Z').to_a

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.cacheObject

Returns the value of attribute cache.



23
24
25
# File 'lib/string_pattern.rb', line 23

def cache
  @cache
end

.cache_valuesObject

Returns the value of attribute cache_values.



23
24
25
# File 'lib/string_pattern.rb', line 23

def cache_values
  @cache_values
end

.dont_repeatObject

Returns the value of attribute dont_repeat.



23
24
25
# File 'lib/string_pattern.rb', line 23

def dont_repeat
  @dont_repeat
end

.national_charsObject

Returns the value of attribute national_chars.



23
24
25
# File 'lib/string_pattern.rb', line 23

def national_chars
  @national_chars
end

.optimisticObject

Returns the value of attribute optimistic.



23
24
25
# File 'lib/string_pattern.rb', line 23

def optimistic
  @optimistic
end

Class Method Details

.analyze(pattern, silent: false) ⇒ Object

Analyze the pattern supplied and returns an object of Pattern structure including: min_length, max_length, symbol_type, required_data, excluded_data, data_provided, string_set, all_characters_set



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/string_pattern.rb', line 43

def StringPattern.analyze(pattern, silent:false)
  return @cache[pattern.to_s] unless @cache[pattern.to_s].nil?
  min_length, max_length, symbol_type=pattern.to_s.scan(/(\d+)-(\d+):(.+)/)[0]
  if min_length.nil?
    min_length, symbol_type=pattern.to_s.scan(/^!?(\d+):(.+)/)[0]
    max_length=min_length
    if min_length.nil?
      puts "pattern argument not valid on StringPattern.generate: #{pattern.inspect}" unless silent
      return pattern.to_s
    end
  end
  
  symbol_type='!'+symbol_type if pattern.to_s[0]=='!'
  min_length=min_length.to_i
  max_length=max_length.to_i

  required_data=Array.new
  excluded_data=Array.new
  required=false
  excluded=false
  data_provided=Array.new
  a=symbol_type
  begin_provided=a.index('[')
  excluded_end_tag=false
  unless begin_provided.nil?
    c=begin_provided+1
    until c==a.size or (a[c..c]==']' and a[c..c+1]!=']]')
      if a[c..c+1]==']]'
        data_provided.push(']')
        c=c+2
      elsif a[c..c+1]=='%%' and !excluded then
        data_provided.push('%')
        c=c+2
      else
        if a[c..c]=='/' and !excluded
          if a[c..c+1]=='//'
            data_provided.push(a[c..c])
            if required
              required_data.push([a[c..c]])
            end
            c=c+1
          else
            if !required
              required=true
            else
              required=false
            end
          end
        else
          if required
            required_data.push([a[c..c]])
          else
            if a[c..c]=='%'
              if a[c..c+1]=='%%' and excluded
                excluded_data.push([a[c..c]])
                c=c+1
              else
                if !excluded
                  excluded=true
                else
                  excluded=false
                  excluded_end_tag=true
                end
              end
            else
              if excluded
                excluded_data.push([a[c..c]])
              end
            end

          end
          if excluded==false and excluded_end_tag==false
            data_provided.push(a[c..c])
          end
          excluded_end_tag=false
        end
        c=c+1
      end
    end
    symbol_type=symbol_type[0..begin_provided].to_s + symbol_type[c..symbol_type.size].to_s
  end

  required=false
  required_symbol=''
  if symbol_type.include?("/")
symbol_type.chars.each {|stc|
  if stc=='/'
  if !required
    required=true
  else
    required=false
  end
  else
  if required
    required_symbol+=stc
  end
  end
}
  end

  national_set=@national_chars.chars

  if symbol_type.include?('L') then
 alpha_set = ALPHA_SET_LOWER + ALPHA_SET_CAPITAL
  elsif symbol_type.include?('x')
    alpha_set=ALPHA_SET_LOWER
    if symbol_type.include?('X')
      alpha_set = alpha_set + ALPHA_SET_CAPITAL
    end
  elsif symbol_type.include?('X')
    alpha_set=ALPHA_SET_CAPITAL
  end
  if symbol_type.include?('T')
    alpha_set=alpha_set+national_set
  end

  unless required_symbol.nil?
if required_symbol.include?('x')
  required_data.push ALPHA_SET_LOWER
end
if required_symbol.include?('X')
  required_data.push ALPHA_SET_CAPITAL
end
if required_symbol.include?('L')
  required_data.push(ALPHA_SET_CAPITAL+ALPHA_SET_LOWER)
end
if required_symbol.include?('T')
  required_data.push national_set
end
required_symbol=required_symbol.downcase
  end
  string_set=Array.new
  all_characters_set=ALPHA_SET_CAPITAL+ALPHA_SET_LOWER+NUMBER_SET+SPECIAL_SET+data_provided+national_set

  if symbol_type.include?('_')
    unless symbol_type.include?('$')
      string_set.push(' ')
    end
    if required_symbol.include?('_')
      required_data.push([' '])
    end
  end

  symbol_type = symbol_type.downcase

  if symbol_type.include?('x') or symbol_type.include?('l') or symbol_type.include?('t')
    string_set = string_set + alpha_set
  end
  if symbol_type.include?('n')
    string_set = string_set + NUMBER_SET
  end
  if symbol_type.include?('$')
    string_set = string_set + SPECIAL_SET
  end
  if symbol_type.include?('*')
    string_set = string_set+all_characters_set
  end
  if data_provided.size!=0
    string_set = string_set + data_provided
  end
  unless required_symbol.empty?
if required_symbol.include?('n')
  required_data.push NUMBER_SET
end
if required_symbol.include?('$')
  required_data.push SPECIAL_SET
end
  end
  unless excluded_data.empty?
    string_set=string_set-excluded_data.flatten
  end
  string_set.uniq!
  @cache[pattern.to_s]=Pattern.new(min_length, max_length, symbol_type, required_data, excluded_data, data_provided,
                     string_set, all_characters_set)
  return @cache[pattern.to_s]
end

.generate(pattern, expected_errors: [], **synonyms) ⇒ Object

Generate a random string based on the pattern supplied (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: "10-15:Ln/x/".generate #generate method on String class (alias: gen) ['(', :'3:N', ')', :'6-8:N'].generate #generate method on Array class (alias: gen) generate("10-15:Ln/x/") #generate Ruby Kernel method generate(['(', :'3:N', ')', :'6-8:N']) #generate Ruby Kernel method "(,3:N,) ,3:N,-,2:N,-,2:N".split(",").generate #>(937) #generate method on Array class (alias: gen) %w3:N ) 1:_ 3:N - 2:N - 2:N.gen #generate method on Array class, using alias gen method Input: pattern: array or string of different patterns. A pattern is a string with this info: "length:symbol_type" or "min_length-max_length:symbol_type" In case an array supplied, the positions using a string pattern should be supplied as symbols if StringPattern.optimistic==false

These are the possible string patterns you will be able to supply: 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. min_length -- minimum length of the string max_length (optional) -- maximum length of the string. If not provided the result will be with the min_length provided symbol_type -- the type of the string we want. you can use a combination of any ot these: x for alpha in lowercase X for alpha in capital letters L for all kind of alpha in capital and lower letters T For the national characters defined on StringPattern.national_chars n for number $ for special characters (includes space) _ for space * all characters [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: %% %characters% the characters we don't want on the resulting string. %% to exclude the character % /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: // We can supply 0 to allow empty strings, this character need to be at the beginning If you want to include the character " use \" If you want to include the character \ use \ If you want to include the character [ use [ Another uses: @ for email Examples: [:"6:X", :"3-8:N"] # 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" [:"6-15:L_N", "fixed text", :"3:N"] # 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" "10-20:LN[=#]" # it will return a string of 10-20 characters containing Letters and/or numbers and/or the characters = and #, for example: eiyweQFWeL#do4Vl "30:TN[#=]/x/" # 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 "10:N[%0%]" # 10 characters length containing numbers and excluding the character 0, for example: 3523497757 "10:N[%0%/AB/]" # 10 characters length containing numbers and excluding the character 0 and necessary to contain the characters A B, for example: 3AA4AA57BB "!10:N[%0%/AB/]" # it will generate a string that doesn't fulfill the pattern supplied, examples: # a6oMQ4JK9g # /Y<N6Aa[ae # 3444439A34B32 "10:N[%0%/AB/]", errors: [:length] # it will generate a string following the pattern and with the errors supplied, in this case, length, example: AB44 Output: the generated string



279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
# File 'lib/string_pattern.rb', line 279

def StringPattern.generate(pattern, expected_errors: [], **synonyms)
  tries = 0
  begin
good_result = true
tries+=1
string=''

expected_errors=synonyms[:errors] if synonyms.keys.include?(:errors)

if expected_errors.kind_of?(Symbol)
  expected_errors=[expected_errors]
end

if pattern.kind_of?(Array)
  pattern.each {|pat|
  if pat.kind_of?(Symbol)
    if pat.to_s.scan(/^!?\d+-?\d*:.+/).size>0
    string<<StringPattern.generate(pat.to_s, expected_errors: expected_errors)
    else
    string<<pat.to_s
    end
  elsif pat.kind_of?(String) then
    if @optimistic and pat.to_s.scan(/^!?\d+-?\d*:.+/).size>0
    string<<StringPattern.generate(pat.to_s, expected_errors: expected_errors)
    else
    string<<pat
    end
  else
    puts "StringPattern.generate: it seems you supplied wrong array of patterns: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
    return ''
  end
  }
  return string
elsif pattern.kind_of?(String) or pattern.kind_of?(Symbol)
  patt=StringPattern.analyze(pattern)
  min_length=patt.min_length
  max_length=patt.max_length
  symbol_type=patt.symbol_type

  required_data=patt.required_data
  excluded_data=patt.excluded_data
  string_set=patt.string_set
  all_characters_set=patt.all_characters_set

  required_chars=Array.new
  unless required_data.size==0
    required_data.each {|rd|
    required_chars<<rd if rd.size==1
    }
    unless excluded_data.size==0
      if (required_chars.flatten & excluded_data.flatten).size>0
      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}"
      return ''
      end
    end
  end
  string_set_not_allowed=Array.new

else
  puts "pattern argument not valid on StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
  return pattern.to_s
end


allow_empty=false
deny_pattern=false
if symbol_type[0..0]=='!'
  deny_pattern=true
  possible_errors=[:length, :value, :required_data, :excluded_data, :string_set_not_allowed]
  (rand(possible_errors.size)+1).times {
  expected_errors<<possible_errors.sample
  }
  expected_errors.uniq!
  if symbol_type[1..1]=='0'
  allow_empty=true
  end
elsif symbol_type[0..0]=='0' then
  allow_empty=true
end
  
if expected_errors.include?(:min_length) or expected_errors.include?(:length) or
  expected_errors.include?(:max_length)
  allow_empty=!allow_empty
elsif expected_errors.include?(:value) or
  expected_errors.include?(:excluded_data) or
  expected_errors.include?(:required_data) or
  expected_errors.include?(:string_set_not_allowed) and allow_empty
  allow_empty=false
end

length=min_length
symbol_type_orig=symbol_type

expected_errors_left=expected_errors.dup

symbol_type=symbol_type_orig

unless deny_pattern
  if required_data.size==0 and expected_errors_left.include?(:required_data)
  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}"
  return ''
  end

  if excluded_data.size==0 and expected_errors_left.include?(:excluded_data)
  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}"
  return ''
  end

  if  expected_errors_left.include?(:string_set_not_allowed)
  string_set_not_allowed=all_characters_set-string_set
  if string_set_not_allowed.size==0 then 
    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}"
    return ''
  end
  end
end

if expected_errors_left.include?(:min_length) or
  expected_errors_left.include?(:max_length) or
  expected_errors_left.include?(:length)
  if expected_errors_left.include?(:min_length) or
    (min_length>0 and expected_errors_left.include?(:length) and rand(2)==0)
  if min_length>0
    if allow_empty
    length=rand(min_length).to_i
    else
    length=rand(min_length-1).to_i+1
    end
    if required_data.size>length and required_data.size<min_length
    length=required_data.size
    end
    expected_errors_left.delete(:length)
    expected_errors_left.delete(:min_length)
  else
    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}"
    return ''
  end
  elsif expected_errors_left.include?(:max_length) or expected_errors_left.include?(:length)
  length=max_length+1+rand(max_length).to_i
  expected_errors_left.delete(:length)
  expected_errors_left.delete(:max_length)
  end
else
  if allow_empty and rand(7)==1
  length=0
  else
  if max_length==min_length
    length=min_length
  else
    length=min_length + rand(max_length - min_length + 1)
  end
  end
end

if deny_pattern
  if required_data.size==0 and expected_errors_left.include?(:required_data)
  expected_errors_left.delete(:required_data)
  end

  if excluded_data.size==0 and expected_errors_left.include?(:excluded_data)
  expected_errors_left.delete(:excluded_data)
  end

  if expected_errors_left.include?(:string_set_not_allowed)
  string_set_not_allowed=all_characters_set-string_set
  if string_set_not_allowed.size==0
    expected_errors_left.delete(:string_set_not_allowed)
  end
  end

  if symbol_type=='!@' and expected_errors_left.size==0 and !expected_errors.include?(:length) and
    (expected_errors.include?(:required_data) or expected_errors.include?(:excluded_data))
  expected_errors_left.push(:value)
  end

end

string = ''
if symbol_type!='@' and symbol_type!='!@' and length!=0 and string_set.size!=0
  if string_set.size!=0
  1.upto(length) {|i| string << string_set.sample.to_s
  }
  end
  if required_data.size>0
  positions_to_set=(0..(string.size-1)).to_a
  required_data.each {|rd|
    if (string.chars & rd).size>0
    rd_to_set=(string.chars & rd).sample
    else
    rd_to_set=rd.sample
    end
    if ((0 ... string.length).find_all {|i| string[i, 1] == rd_to_set}).size==0
    if positions_to_set.size==0
      puts "pattern not valid on StringPattern.generate, not possible to generate a valid string: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
      return ''
    else
      k=positions_to_set.sample
      string[k]=rd_to_set
      positions_to_set.delete(k)
    end
    else
    k=((0 ... string.length).find_all {|i| string[i, 1] == rd_to_set}).sample
    positions_to_set.delete(k)
    end
  }
  end
  excluded_data.each {|ed|
  if (string.chars & ed).size>0
    (string.chars & ed).each {|s|
    string.gsub!(s, string_set.sample)
    }
  end
  }

  if expected_errors_left.include?(:value)
  string_set_not_allowed=all_characters_set-string_set if string_set_not_allowed.size==0
  if string_set_not_allowed.size==0
    puts "Not possible to generate a non valid string on StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
    return ''
  end
  (rand(string.size)+1).times {
    string[rand(string.size)]=(all_characters_set-string_set).sample
  }
  expected_errors_left.delete(:value)
  end

  if expected_errors_left.include?(:required_data) and required_data.size>0
  (rand(required_data.size)+1).times {
    chars_to_remove=required_data.sample
    chars_to_remove.each {|char_to_remove|
    string.gsub!(char_to_remove, (string_set-chars_to_remove).sample)
    }
  }
  expected_errors_left.delete(:required_data)
  end

  if expected_errors_left.include?(:excluded_data) and excluded_data.size>0
  (rand(string.size)+1).times {
    string[rand(string.size)]=excluded_data.sample.sample
  }
  expected_errors_left.delete(:excluded_data)
  end

  if expected_errors_left.include?(:string_set_not_allowed) 
  string_set_not_allowed=all_characters_set-string_set if string_set_not_allowed.size==0
  if string_set_not_allowed.size>0
    (rand(string.size)+1).times {
      string[rand(string.size)]=string_set_not_allowed.sample
    }
    expected_errors_left.delete(:string_set_not_allowed)
  end
  end

elsif (symbol_type=='@' or symbol_type=='!@') and length>0
  if min_length>6 and length<6
  length=6
  end
  if deny_pattern and
    (expected_errors.include?(:required_data) or expected_errors.include?(:excluded_data) or
      expected_errors.include?(:string_set_not_allowed))
  expected_errors_left.push(:value)
  expected_errors.push(:value)
  expected_errors.uniq!
  expected_errors_left.uniq!
  end

  expected_errors_left_orig=expected_errors_left.dup
  tries=0
  begin
  expected_errors_left=expected_errors_left_orig.dup
  tries+=1
  string=''
  alpha_set=ALPHA_SET_LOWER + ALPHA_SET_CAPITAL
  string_set=alpha_set + NUMBER_SET + ['.'] + ['_'] + ['-']
  string_set_not_allowed=all_characters_set-string_set

  extension='.'
  at_sign='@'

  if expected_errors_left.include?(:value)
    if rand(2)==1
    extension=(all_characters_set-['.']).sample
    expected_errors_left.delete(:value)
    expected_errors_left.delete(:required_data)
    end
    if rand(2)==1
    1.upto(rand(7)) {|i| extension << alpha_set.sample.downcase
    }
    (rand(extension.size)+1).times {
      extension[rand(extension.size)]=(string_set-alpha_set-['.']).sample
    }
    expected_errors_left.delete(:value)
    else
    1.upto(rand(3)+2) {|i| extension << alpha_set.sample.downcase
    }
    end
    if rand(2)==1
    at_sign=(string_set-['@']).sample
    expected_errors_left.delete(:value)
    expected_errors_left.delete(:required_data)
    end
  else
    if length>6
    1.upto(rand(3)+2) {|i| extension << alpha_set.sample.downcase
    }
    else
    1.upto(2) {|i| extension << alpha_set.sample.downcase
    }
    end
  end
  length_e=length-extension.size - 1
  length1=rand(length_e-1) + 1
  length2=length_e-length1
  1.upto(length1) {|i| string << string_set.sample}

  string << at_sign

  domain=''
  domain_set=alpha_set + NUMBER_SET + ['.'] + ['-']
  1.upto(length2) {|i| domain << domain_set.sample.downcase
  }

  if expected_errors.include?(:value) and rand(2)==1 and domain.size>0
    (rand(domain.size)+1).times {
    domain[rand(domain.size)]=(all_characters_set-domain_set).sample
    }
    expected_errors_left.delete(:value)
  end
  string << domain << extension

  if expected_errors_left.include?(:value) or expected_errors_left.include?(:string_set_not_allowed)
    (rand(string.size)+1).times {
    string[rand(string.size)]=string_set_not_allowed.sample
    }
    expected_errors_left.delete(:value)
    expected_errors_left.delete(:string_set_not_allowed)
  end

  error_regular_expression=false

  if deny_pattern and expected_errors.include?(:length)
    good_result=true #it is already with wrong length

  else
    # I'm doing this because many times the regular expression checking hangs with these characters

    wrong=%w(.. __ -- ._ _. .- -. _- -_ @. @_ @- .@ _@ -@ @@)
    if !(Regexp.union(*wrong) === string) #don't include any or the wrong strings

    if string.index('@').to_i>0 and
      string[0..(string.index('@')-1)].scan(/([a-z0-9]+([\+\._\-][a-z0-9]|)*)/i).join==string[0..(string.index('@')-1)] and
      string[(string.index('@')+1)..-1].scan(/([0-9a-z]+([\.-][a-z0-9]|)*)/i).join==string[string[(string.index('@')+1)..-1]]
      error_regular_expression=false
    else
      error_regular_expression=true
    end
    else
    error_regular_expression=true
    end

    if expected_errors.size==0
    if error_regular_expression
      good_result=false
    else
      good_result=true
    end
    elsif expected_errors_left.size==0 and
      (expected_errors-[:length, :min_length, :max_length]).size==0
    good_result=true
    elsif expected_errors!=[:length]
    if !error_regular_expression
      good_result=false
    elsif expected_errors.include?(:value)
      good_result=true
    end
    end
  end

  end until good_result or tries>100
  unless good_result
  puts "Not possible to generate an email on StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
  return ''
  end
end
if @dont_repeat
  if @cache_values[pattern.to_s].nil?
    @cache_values[pattern.to_s]=Array.new()
    @cache_values[pattern.to_s].push(string)
    good_result=true
  elsif @cache_values[pattern.to_s].include?(string)
    good_result = false
  else
    @cache_values[pattern.to_s].push(string)
    good_result=true
  end
end
  end until good_result or tries>10000
  unless good_result
 puts "Not possible to generate the string on StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
 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"
 return ''
  end
  
  return string
end

.validate(text: '', pattern: '', expected_errors: [], not_expected_errors: [], **synonyms) ⇒ Object

This method is defined to validate if the text_to_validate supplied follows the pattern It works also with array of patterns but in that case will return only true or false input: text (String) (synonyms: text_to_validate, validate) -- The text to validate pattern -- symbol with this info: "length:symbol_type" or "min_length-max_length:symbol_type" min_length -- minimum length of the string max_length (optional) -- maximum length of the string. If not provided the result will be with the min_length provided symbol_type -- the type of the string we want. expected_errors (Array of symbols) (optional) (synonyms: errors) -- :length, :min_length, :max_length, :value, :required_data, :excluded_data, :string_set_not_allowed 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 example: validate(text: "This text will be validated", pattern: :"10-20:Xn", expected_errors: [:value, :max_length])

Output: if expected_errors and not_expected_errors are not supplied: an array with all detected errors if expected_errors or not_expected_errors supplied: true or false if array of patterns supplied, it will return true or false



702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
# File 'lib/string_pattern.rb', line 702

def StringPattern.validate(text: '', pattern: '', expected_errors: [], not_expected_errors: [], **synonyms)
  text_to_validate=text
  text_to_validate=synonyms[:text_to_validate] if synonyms.keys.include?(:text_to_validate)
  text_to_validate=synonyms[:validate] if synonyms.keys.include?(:validate)
  expected_errors=synonyms[:errors] if synonyms.keys.include?(:errors)
  not_expected_errors=synonyms[:not_errors] if synonyms.keys.include?(:not_errors)
  not_expected_errors=synonyms[:non_expected_errors] if synonyms.keys.include?(:non_expected_errors)
  #:length, :min_length, :max_length, :value, :required_data, :excluded_data, :string_set_not_allowed

  if (expected_errors.include?(:min_length) or expected_errors.include?(:max_length)) and !expected_errors.include?(:length)
    expected_errors.push(:length)
  end
  if (not_expected_errors.include?(:min_length) or not_expected_errors.include?(:max_length)) and !not_expected_errors.include?(:length)
    not_expected_errors.push(:length)
  end
  if pattern.kind_of?(Array) and pattern.size==1
    pattern=pattern[0]
  elsif pattern.kind_of?(Array) and pattern.size>1 then
    total_min_length=0
    total_max_length=0
    all_errors_collected=Array.new
    result=true
    num_patt=0
    patterns=Array.new
    pattern.each {|pat|
      if (pat.kind_of?(String) and (!StringPattern.optimistic or
          (StringPattern.optimistic and pat.to_s.scan(/(\d+)-(\d+):(.+)/).size==0 and pat.to_s.scan(/^!?(\d+):(.+)/).size==0))) #fixed text

        symbol_type=''
        min_length=max_length=pat.length
      elsif pat.kind_of?(Symbol) or (pat.kind_of?(String) and StringPattern.optimistic and
          (pat.to_s.scan(/(\d+)-(\d+):(.+)/).size>0 or pat.to_s.scan(/^!?(\d+):(.+)/).size>0))
        patt=StringPattern.analyze(pat)
        min_length=patt.min_length
        max_length=patt.max_length
        symbol_type=patt.symbol_type
      else
        puts "String pattern class not supported (#{pat.class} for #{pat})"
      end

      patterns.push({pattern: pat, min_length: min_length, max_length: max_length, symbol_type: symbol_type})

      total_min_length+=min_length
      total_max_length+=max_length

      if num_patt==(pattern.size-1) # i am in the last one

        if text_to_validate.length<total_min_length
          all_errors_collected.push(:length)
          all_errors_collected.push(:min_length)
        end

        if text_to_validate.length>total_max_length
          all_errors_collected.push(:length)
          all_errors_collected.push(:max_length)
        end

      end
      num_patt+=1


    }

    num_patt=0
    patterns.each {|patt|

      tmp_result=false
      (patt[:min_length]..patt[:max_length]).each {|n|
        res=StringPattern.validate(text: text_to_validate[0..n-1], pattern: patt[:pattern], not_expected_errors: not_expected_errors)
        if res.kind_of?(Array)
          all_errors_collected+=res
        end

        if res.kind_of?(TrueClass) or (res.kind_of?(Array) and res.size==0) #valid

          #we pass in the next one the rest of the pattern array list: pattern: pattern[num_patt+1..pattern.size]

          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)

          if res.kind_of?(Array)
            if ((all_errors_collected + res)-expected_errors).size>0
              tmp_result=false
            else
              all_errors_collected+=res
              tmp_result=true
            end
          elsif res.kind_of?(TrueClass) then
            tmp_result=true
          end
          return true if tmp_result
        end
      }

      unless tmp_result
        return false
      end
      num_patt+=1
    }
    return result
  end

  if (pattern.kind_of?(String) and (!StringPattern.optimistic or
      (StringPattern.optimistic and pattern.to_s.scan(/(\d+)-(\d+):(.+)/).size==0 and pattern.to_s.scan(/^!?(\d+):(.+)/).size==0))) #fixed text

    symbol_type=''
    min_length=max_length=pattern.length
  else #symbol

    patt=StringPattern.analyze(pattern)
    min_length=patt.min_length
    max_length=patt.max_length
    symbol_type=patt.symbol_type

    required_data=patt.required_data
    excluded_data=patt.excluded_data
    string_set=patt.string_set
    all_characters_set=patt.all_characters_set

    required_chars=Array.new
    required_data.each {|rd|
      required_chars<<rd if rd.size==1
    }
    if (required_chars.flatten & excluded_data.flatten).size>0
      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}"
      return ''
    end

  end

  if text_to_validate.nil?
    return false
  end
  detected_errors=Array.new

  if text_to_validate.length<min_length
    detected_errors.push(:min_length)
    detected_errors.push(:length)
  end
  if text_to_validate.length>max_length
    detected_errors.push(:max_length)
    detected_errors.push(:length)
  end

  if symbol_type=='' #fixed text

    if pattern.to_s!=text.to_s #not equal

      detected_errors.push(:value)
      detected_errors.push(:required_data)
    end
  else # pattern supplied

    if symbol_type!='@'
      if required_data.size>0
        required_data.each {|rd|
          if (text_to_validate.chars & rd).size==0
            detected_errors.push(:value)
            detected_errors.push(:required_data)
            break
          end
        }
      end
      if excluded_data.size>0
        if (excluded_data & text_to_validate.chars).size>0
          detected_errors.push(:value)
          detected_errors.push(:excluded_data)
        end
      end
      string_set_not_allowed=all_characters_set-string_set
      text_to_validate.chars.each {|st|
        if string_set_not_allowed.include?(st)
          detected_errors.push(:value)
          detected_errors.push(:string_set_not_allowed)
          break
        end
      }
    else #symbol_type=="@"

      string=text_to_validate
      wrong=%w(.. __ -- ._ _. .- -. _- -_ @. @_ @- .@ _@ -@ @@)
      if !(Regexp.union(*wrong) === string) #don't include any or the wrong strings

        if string.index('@').to_i>0 and
            string[0..(string.index('@')-1)].scan(/([a-z0-9]+([\+\._\-][a-z0-9]|)*)/i).join==string[0..(string.index('@')-1)] and
            string[(string.index('@')+1)..-1].scan(/([0-9a-z]+([\.-][a-z0-9]|)*)/i).join==string[string[(string.index('@')+1)..-1]]
          error_regular_expression=false
        else
          error_regular_expression=true
        end
      else
        error_regular_expression=true
      end

      if error_regular_expression
        detected_errors.push(:value)
      end

    end
  end

  if expected_errors.size==0 and not_expected_errors.size==0
    return detected_errors
  else
    if expected_errors & detected_errors == expected_errors
      if (not_expected_errors & detected_errors).size > 0
        return false
      else
        return true
      end
    else
      return false
    end
  end
end