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 default_infinite: (Integer, default: 10) In case using regular expressions the maximum when using * or + for repetitions

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.



25
26
27
# File 'lib/string_pattern.rb', line 25

def cache
  @cache
end

.cache_valuesObject

Returns the value of attribute cache_values.



25
26
27
# File 'lib/string_pattern.rb', line 25

def cache_values
  @cache_values
end

.default_infiniteObject

Returns the value of attribute default_infinite.



25
26
27
# File 'lib/string_pattern.rb', line 25

def default_infinite
  @default_infinite
end

.dont_repeatObject

Returns the value of attribute dont_repeat.



25
26
27
# File 'lib/string_pattern.rb', line 25

def dont_repeat
  @dont_repeat
end

.national_charsObject

Returns the value of attribute national_chars.



25
26
27
# File 'lib/string_pattern.rb', line 25

def national_chars
  @national_chars
end

.optimisticObject

Returns the value of attribute optimistic.



25
26
27
# File 'lib/string_pattern.rb', line 25

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



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
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/string_pattern.rb', line 46

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
  if symbol_type[-1]=="&"
    symbol_type.chop!
    unique = true
  else
    unique = false
  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
  else
    alpha_set = []
  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, unique)
  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



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
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
# File 'lib/string_pattern.rb', line 290

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?(Array) # for the case it is one of the values
          pat = pat.sample
        end

        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
    elsif pattern.kind_of?(Regexp)
      return generate(pattern.to_sp, expected_errors: expected_errors)
    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
    if pattern.kind_of?(Symbol) and patt.unique
      if @cache_values[pattern.__id__].nil?
        @cache_values[pattern.__id__] = Array.new()
        @cache_values[pattern.__id__].push(string)
        good_result = true
      elsif @cache_values[pattern.__id__].include?(string)
        good_result = false
      else
        @cache_values[pattern.__id__].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



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
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
# File 'lib/string_pattern.rb', line 731

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