string_pattern 2.1.0 → 2.1.1

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