string_pattern 2.0.0 → 2.1.0

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