string_pattern 2.2.3 → 2.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c451e4295f5c62ea8b7c8113815a0c7a2b5c70a739826fdf651a7fb99a75be48
4
- data.tar.gz: 2445e4958a2bf96b70e0ce99f4df31ab02a61183a496289a1b20b3360e2fecaf
3
+ metadata.gz: 47172de723c22b8175a1119aa51c065056ec4eab029cfb989b4fa4c9bc20d845
4
+ data.tar.gz: c9e9996c3f5ec59813b157a9f818c10723fb1b9f95a1ed871ba80219d04bbe6e
5
5
  SHA512:
6
- metadata.gz: 650a16f6a2cda5c762f2f685b4fe515848135a6c4995ba08504ec80435696d4e1ab6289adaf4aae839ca0d77978338d2603411a7330fe2db0f2bede587554cfd
7
- data.tar.gz: 996deb9300188c286dc0a051f744262367f928192e5c30fcd981f7f964283463f2dc606c4876163378397b767ed235f51bc749bac55e801866810aa19318da91
6
+ metadata.gz: 840a84447c523a01e21657fa772c190388a1401e41d254e5340fe9bd081eb0740ba200161c0a29e06dcfa841632c26d51346da53bf2c5a8f93ad3459aa455930
7
+ data.tar.gz: f53b2b1ef110f8e20b95e7e5bafac00067bbc782e2817fa3292cbd4b60492454b171133b80b8808e737606c83dd8f2df59d5d57447bcc5cc225d6239c0edb7e3
data/README.md CHANGED
@@ -3,6 +3,11 @@
3
3
  [![Gem Version](https://badge.fury.io/rb/string_pattern.svg)](https://rubygems.org/gems/string_pattern)
4
4
  [![Build Status](https://travis-ci.com/MarioRuiz/string_pattern.svg?branch=master)](https://github.com/MarioRuiz/string_pattern)
5
5
  [![Coverage Status](https://coveralls.io/repos/github/MarioRuiz/string_pattern/badge.svg?branch=master)](https://coveralls.io/github/MarioRuiz/string_pattern?branch=master)
6
+ ![Gem](https://img.shields.io/gem/dt/string_pattern)
7
+ ![GitHub commit activity](https://img.shields.io/github/commit-activity/y/MarioRuiz/string_pattern)
8
+ ![GitHub last commit](https://img.shields.io/github/last-commit/MarioRuiz/string_pattern)
9
+ ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/MarioRuiz/string_pattern)
10
+
6
11
 
7
12
  With this gem, you can easily generate strings supplying a very simple pattern. Even generate random words in English or Spanish.
8
13
  Also, you can validate if a text fulfills a specific pattern or even generate a string following a pattern and returning the wrong length, value... for testing your applications. Perfect to be used in test data factories.
@@ -313,12 +318,9 @@ Examples:
313
318
 
314
319
  If you need to validate if a specific text is fulfilling the pattern you can use the validate method.
315
320
 
316
- If a string pattern supplied and no other parameters supplied the output will be an array with the errors detected.
317
-
318
-
319
- Possible output values, empty array (validation without errors detected) or one or more of: :min_length, :max_length, :length, :value, :string_set_not_allowed, :required_data, :excluded_data
321
+ When you supply a single pattern and do **not** supply `expected_errors` or `not_expected_errors`, the method returns an **array of error symbols**: an empty array `[]` when the text is valid, or one or more of `:min_length`, `:max_length`, `:length`, `:value`, `:string_set_not_allowed`, `:required_data`, `:excluded_data` when invalid.
320
322
 
321
- In case an array of patterns supplied it will return only true or false
323
+ When an array of patterns is supplied, the method returns only `true` or `false`.
322
324
 
323
325
  Examples:
324
326
 
@@ -428,6 +430,80 @@ StringPattern.optimistic = true
428
430
  #>SAAERfixedtext988
429
431
  ```
430
432
 
433
+ #### block_list
434
+
435
+ To specify which words will be avoided from the results
436
+
437
+ ```ruby
438
+ StringPattern.block_list = ['example', 'wrong', 'ugly']
439
+ StringPattern.block_list_enabled = true
440
+ "2-20:Tn".gen #>AAñ34Ef99éNOP
441
+ ```
442
+
443
+ #### StringPattern.analyze
444
+
445
+ To inspect how a pattern is parsed without generating or validating:
446
+
447
+ ```ruby
448
+ p = StringPattern.analyze("10-20:LN/x/")
449
+ # => #<Struct min_length=10, max_length=20, symbol_type="LN/x/", required_data=..., string_set=..., unique=false>
450
+ p.min_length # => 10
451
+ p.max_length # => 20
452
+ p.symbol_type # => "LN/x/"
453
+ ```
454
+
455
+ Useful for debugging or building tools on top of the pattern DSL. Invalid patterns return the pattern string; use `silent: true` to avoid logging.
456
+
457
+ #### Error handling and logging
458
+
459
+ By default, when generation is impossible (e.g. invalid pattern or `dont_repeat` exhausted), `generate` returns an empty string `""` and a message is printed. You can:
460
+
461
+ - Set `StringPattern.logger = Logger.new($stderr)` to send messages to a logger instead of `puts`.
462
+ - Set `StringPattern.raise_on_error = true` to raise `StringPattern::GenerationImpossibleError` or `StringPattern::InvalidPatternError` instead of returning `""`.
463
+
464
+ #### Reproducible generation (seed)
465
+
466
+ Pass `seed:` to get the same string for the same pattern in tests:
467
+
468
+ ```ruby
469
+ "10:N".gen(seed: 42) # => same result every time
470
+ ```
471
+
472
+ #### Batch generation (sample)
473
+
474
+ Generate up to `n` distinct strings without mutating the global dont_repeat cache:
475
+
476
+ ```ruby
477
+ StringPattern.sample("4:N", 10) # => array of 10 distinct 4-digit strings
478
+ ```
479
+
480
+ #### Boolean validation (valid?)
481
+
482
+ Check if text matches a pattern without building the full error list:
483
+
484
+ ```ruby
485
+ StringPattern.valid?(text: "user@domain.com", pattern: "14-40:@") # => true
486
+ ```
487
+
488
+ #### UUID
489
+
490
+ Generate a random UUID v4 or validate one:
491
+
492
+ ```ruby
493
+ StringPattern.uuid # => "550e8400-e29b-41d4-a716-446655440000"
494
+ StringPattern.valid_uuid?(some_str) # => true or false
495
+ ```
496
+
497
+ #### block_list as Proc
498
+
499
+ You can set `block_list` to a Proc for custom blocking:
500
+
501
+ ```ruby
502
+ StringPattern.block_list = ->(s) { s.include?("forbidden") }
503
+ StringPattern.block_list_enabled = true
504
+ ```
505
+
506
+
431
507
  ## Contributing
432
508
 
433
509
  Bug reports and pull requests are welcome on GitHub at https://github.com/marioruiz/string_pattern.
@@ -90,11 +90,14 @@ class Regexp
90
90
  pats = ""
91
91
  patg = [] # for (aa|bb|cc) group
92
92
  set = false
93
+ set_negate = false
94
+ options = []
93
95
  capture = false
94
96
 
95
97
  range = ""
96
98
  fixed_text = false
97
- last_char = (regexp.to_s.gsub("?-mix:", "").length) - 2
99
+ options = regexp.to_s.scan(/\A\(\?([mix]*)\-[mix]*:/).join.split('')
100
+ last_char = (regexp.to_s.gsub(/\A\(\?[mix]*\-[mix]*:/, "").length) - 2
98
101
  Regexp::Scanner.scan regexp do |type, token, text, ts, te|
99
102
  if type == :escape
100
103
  if token == :dot
@@ -103,7 +106,7 @@ class Regexp
103
106
  elsif token == :literal and text.size == 2
104
107
  text = text[1]
105
108
  else
106
- puts "Report token not controlled: type: #{type}, token: #{token}, text: '#{text}' [#{ts}..#{te}]"
109
+ StringPattern.log_message("Report token not controlled: type: #{type}, token: #{token}, text: '#{text}' [#{ts}..#{te}]")
107
110
  end
108
111
  end
109
112
 
@@ -126,9 +129,9 @@ class Regexp
126
129
  pata[-1] += pats.chop
127
130
  else
128
131
  if pats.size == 2
129
- pata << pats.chop #jal
132
+ pata << pats.chop
130
133
  else
131
- pata << "1:[#{pats}" #jal
134
+ pata << "1:[#{pats}"
132
135
  end
133
136
  if last_char == te and type == :literal and token == :literal
134
137
  pata << text
@@ -147,7 +150,6 @@ class Regexp
147
150
  pats = ""
148
151
  end
149
152
  fixed_text = false
150
-
151
153
  case token
152
154
  when :open
153
155
  set = true
@@ -158,7 +160,13 @@ class Regexp
158
160
  if pats[-1] == "["
159
161
  pats.chop!
160
162
  else
161
- pats += "]"
163
+ if set_negate
164
+ pats+="%]*"
165
+ set_negate = false
166
+ else
167
+ pats += "]"
168
+ end
169
+
162
170
  end
163
171
  elsif type == :group
164
172
  capture = false
@@ -169,6 +177,11 @@ class Regexp
169
177
  pats = ""
170
178
  end
171
179
  end
180
+ when :negate
181
+ if set and pats[-1] == '['
182
+ pats+="%"
183
+ set_negate = true
184
+ end
172
185
  when :capture
173
186
  capture = true if type == :group
174
187
  when :alternation
@@ -177,11 +190,11 @@ class Regexp
177
190
  patg << pats
178
191
  pats = ""
179
192
  elsif patg.empty?
180
- # for the case the first element was not added to patg and was on pata fex: (a+|b|c)
181
193
  patg << pata.pop
182
194
  end
183
195
  end
184
196
  when :range
197
+ pats.chop! if options.include?('i')
185
198
  range = pats[-1]
186
199
  pats.chop!
187
200
  when :digit
@@ -212,28 +225,44 @@ class Regexp
212
225
  pats = text[-1]
213
226
  else
214
227
  pats += text
228
+ pats += text.upcase if options.include?('i')
215
229
  end
216
230
  else
217
231
  range = range + "-" + text
218
232
  if range == "a-z"
219
- pats = "x" + pats
233
+ if options.include?('i')
234
+ pats = "L" + pats
235
+ else
236
+ pats = "x" + pats
237
+ end
220
238
  elsif range == "A-Z"
221
- pats = "X" + pats
239
+ if options.include?('i')
240
+ pats = "L" + pats
241
+ else
242
+ pats = "X" + pats
243
+ end
222
244
  elsif range == "0-9"
223
245
  pats = "n" + pats
224
246
  else
225
- pats += if set
226
- (range[0]..range[2]).to_a.join
227
- else
228
- "[" + (range[0]..range[2]).to_a.join + "]"
229
- end
247
+ if set
248
+ pats += (range[0]..range[2]).to_a.join
249
+ if options.include?('i')
250
+ pats += (range[0]..range[2]).to_a.join.upcase
251
+ end
252
+ else
253
+ trange = (range[0]..range[2]).to_a.join
254
+ if options.include?('i')
255
+ trange += trange.upcase
256
+ end
257
+ pats += "[" + trange + "]"
258
+ end
230
259
  end
231
260
  range = ""
232
261
  end
233
262
  pats = "[" + pats + "]" unless set
234
263
  when :interval
235
264
  size = text.sub(",", "-").sub("{", "").sub("}", "")
236
- size.chop! if size[-1] == "-"
265
+ size+=(default_infinite+size.chop.to_i).to_s if size[-1] == "-"
237
266
  pats = size + ":" + pats
238
267
  if !patg.empty?
239
268
  patg << pats
@@ -269,11 +298,11 @@ class Regexp
269
298
  end
270
299
  if pats != ""
271
300
  if pata.empty?
272
- if pats[0] == "[" and pats[-1] == "]" #fex: /[12ab]/
301
+ if pats[0] == "[" and pats[-1] == "]"
273
302
  pata = ["1:#{pats}"]
274
303
  end
275
304
  else
276
- pata[-1] += pats[1] #fex: /allo/
305
+ pata[-1] += pats[1]
277
306
  end
278
307
  end
279
308
  if pata.size == 1 and pata[0].kind_of?(String)
@@ -295,7 +324,7 @@ module Kernel
295
324
  if pattern.is_a?(String) || pattern.is_a?(Array) || pattern.is_a?(Symbol) || pattern.is_a?(Regexp)
296
325
  StringPattern.generate(pattern, expected_errors: expected_errors, **synonyms)
297
326
  else
298
- puts " Kernel generate method: class not recognized:#{pattern.class}"
327
+ StringPattern.log_message(" Kernel generate method: class not recognized:#{pattern.class}")
299
328
  end
300
329
  end
301
330
 
@@ -1,8 +1,8 @@
1
1
  class StringPattern
2
- ###############################################
3
- # Analyze the pattern supplied and returns an object of Pattern structure including:
4
- # min_length, max_length, symbol_type, required_data, excluded_data, data_provided, string_set, all_characters_set
5
- ###############################################
2
+ # Analyzes a pattern string and returns a Pattern struct.
3
+ # @param pattern [String, Symbol] Pattern in format "length:type" or "min-max:type" (e.g. "10:N", "5-15:L")
4
+ # @param silent [Boolean] If true, invalid patterns do not log a message.
5
+ # @return [Struct, String] Pattern struct with min_length, max_length, symbol_type, required_data, excluded_data, data_provided, string_set, all_characters_set, unique; or the pattern string if invalid.
6
6
  def StringPattern.analyze(pattern, silent: false)
7
7
  #unless @cache[pattern.to_s].nil?
8
8
  # return Pattern.new(@cache[pattern.to_s].min_length.clone, @cache[pattern.to_s].max_length.clone,
@@ -16,7 +16,7 @@ class StringPattern
16
16
  min_length, symbol_type = pattern.to_s.scan(/^!?(\d+):(.+)/)[0]
17
17
  max_length = min_length
18
18
  if min_length.nil?
19
- puts "pattern argument not valid on StringPattern.generate: #{pattern.inspect}" unless silent
19
+ StringPattern.log_message("pattern argument not valid on StringPattern.generate: #{pattern.inspect}") unless silent
20
20
  return pattern.to_s
21
21
  end
22
22
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ class StringPattern
4
+ # Validates email format using the same rules as pattern type @:
5
+ # - Forbids consecutive/adjacent invalid sequences (.. __ -- etc.)
6
+ # - Local part: [a-z0-9]+([\+\._\-][a-z0-9])*
7
+ # - Domain part: [0-9a-z]+([\.-][a-z0-9])*
8
+ def self.valid_email?(string)
9
+ return false if string.nil? || !string.is_a?(String)
10
+ return false if string.index("@").to_i <= 0
11
+
12
+ wrong = %w(.. __ -- ._ _. .- -. _- -_ @. @_ @- .@ _@ -@ @@)
13
+ return false if Regexp.union(*wrong) === string
14
+
15
+ local = string[0..(string.index("@") - 1)]
16
+ domain = string[(string.index("@") + 1)..-1]
17
+ local_ok = local.scan(/([a-z0-9]+([\+\._\-][a-z0-9]|)*)/i).join == local
18
+ domain_ok = domain.scan(/([0-9a-z]+([\.-][a-z0-9]|)*)/i).join == domain
19
+ local_ok && domain_ok
20
+ end
21
+ end
@@ -64,6 +64,8 @@ class StringPattern
64
64
  # the generated string
65
65
  ###############################################
66
66
  def StringPattern.generate(pattern, expected_errors: [], **synonyms)
67
+ seed_given = synonyms.key?(:seed)
68
+ saved_rng = seed_given ? srand(synonyms[:seed]) : nil
67
69
  tries = 0
68
70
  begin
69
71
  good_result = true
@@ -95,7 +97,7 @@ class StringPattern
95
97
  string << pat
96
98
  end
97
99
  else
98
- puts "StringPattern.generate: it seems you supplied wrong array of patterns: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
100
+ StringPattern.log_message("StringPattern.generate: it seems you supplied wrong array of patterns: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}")
99
101
  return ""
100
102
  end
101
103
  }
@@ -120,7 +122,7 @@ class StringPattern
120
122
  }
121
123
  unless excluded_data.size == 0
122
124
  if (required_chars.flatten & excluded_data.flatten).size > 0
123
- 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}"
125
+ StringPattern.log_message("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}")
124
126
  return ""
125
127
  end
126
128
  end
@@ -130,7 +132,7 @@ class StringPattern
130
132
  elsif pattern.kind_of?(Regexp)
131
133
  return generate(pattern.to_sp, expected_errors: expected_errors)
132
134
  else
133
- puts "pattern argument not valid on StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
135
+ StringPattern.log_message("pattern argument not valid on StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}")
134
136
  return pattern.to_s
135
137
  end
136
138
 
@@ -169,12 +171,12 @@ class StringPattern
169
171
 
170
172
  unless deny_pattern
171
173
  if required_data.size == 0 and expected_errors_left.include?(:required_data)
172
- 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}"
174
+ StringPattern.log_message("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}")
173
175
  return ""
174
176
  end
175
177
 
176
178
  if excluded_data.size == 0 and expected_errors_left.include?(:excluded_data)
177
- 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}"
179
+ StringPattern.log_message("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}")
178
180
  return ""
179
181
  end
180
182
 
@@ -182,7 +184,7 @@ class StringPattern
182
184
  string_set_not_allowed = all_characters_set - string_set
183
185
 
184
186
  if string_set_not_allowed.size == 0
185
- 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}"
187
+ StringPattern.log_message("all characters are allowed so it won't be possible to generate a wrong string. StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}")
186
188
  return ""
187
189
  end
188
190
  end
@@ -205,7 +207,7 @@ class StringPattern
205
207
  expected_errors_left.delete(:length)
206
208
  expected_errors_left.delete(:min_length)
207
209
  else
208
- 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}"
210
+ StringPattern.log_message("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}")
209
211
  return ""
210
212
  end
211
213
  elsif expected_errors_left.include?(:max_length) or expected_errors_left.include?(:length)
@@ -264,7 +266,7 @@ class StringPattern
264
266
  end
265
267
  if ((0...string.length).find_all { |i| string[i, 1] == rd_to_set }).size == 0
266
268
  if positions_to_set.size == 0
267
- puts "pattern not valid on StringPattern.generate, not possible to generate a valid string: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
269
+ StringPattern.log_message("pattern not valid on StringPattern.generate, not possible to generate a valid string: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}")
268
270
  return ""
269
271
  else
270
272
  k = positions_to_set.sample
@@ -289,7 +291,7 @@ class StringPattern
289
291
  string_set_not_allowed = all_characters_set - string_set if string_set_not_allowed.size == 0
290
292
 
291
293
  if string_set_not_allowed.size == 0
292
- puts "Not possible to generate a non valid string on StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
294
+ StringPattern.log_message("Not possible to generate a non valid string on StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}")
293
295
  return ""
294
296
  end
295
297
  (rand(string.size) + 1).times {
@@ -502,24 +504,11 @@ class StringPattern
502
504
  expected_errors_left.delete(:string_set_not_allowed)
503
505
  end
504
506
 
505
- error_regular_expression = false
507
+ error_regular_expression = !StringPattern.valid_email?(string)
506
508
 
507
509
  if deny_pattern and expected_errors.include?(:length)
508
510
  good_result = true #it is already with wrong length
509
511
  else
510
- # I'm doing this because many times the regular expression checking hangs with these characters
511
- wrong = %w(.. __ -- ._ _. .- -. _- -_ @. @_ @- .@ _@ -@ @@)
512
- if !(Regexp.union(*wrong) === string) #don't include any or the wrong strings
513
- if string.index("@").to_i > 0 and
514
- string[0..(string.index("@") - 1)].scan(/([a-z0-9]+([\+\._\-][a-z0-9]|)*)/i).join == string[0..(string.index("@") - 1)] and
515
- string[(string.index("@") + 1)..-1].scan(/([0-9a-z]+([\.-][a-z0-9]|)*)/i).join == string[string[(string.index("@") + 1)..-1]]
516
- error_regular_expression = false
517
- else
518
- error_regular_expression = true
519
- end
520
- else
521
- error_regular_expression = true
522
- end
523
512
 
524
513
  if expected_errors.size == 0
525
514
  if error_regular_expression
@@ -540,7 +529,9 @@ class StringPattern
540
529
  end
541
530
  end until good_result or tries > 100
542
531
  unless good_result
543
- puts "Not possible to generate an email on StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
532
+ msg = "Not possible to generate an email on StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
533
+ raise StringPattern::GenerationImpossibleError, msg if @raise_on_error
534
+ StringPattern.log_message(msg)
544
535
  return ""
545
536
  end
546
537
  end
@@ -568,12 +559,28 @@ class StringPattern
568
559
  good_result = true
569
560
  end
570
561
  end
562
+ if @block_list_enabled
563
+ if @block_list.respond_to?(:call)
564
+ good_result = false if @block_list.call(string)
565
+ elsif @block_list.is_a?(Array)
566
+ @block_list.each do |bl|
567
+ if string.match?(/#{bl}/i)
568
+ good_result = false
569
+ break
570
+ end
571
+ end
572
+ end
573
+ end
571
574
  end until good_result or tries > 10000
572
575
  unless good_result
573
- puts "Not possible to generate the string on StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
574
- 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"
576
+ msg = "Not possible to generate the string on StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
577
+ msg += "\nTake 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"
578
+ raise StringPattern::GenerationImpossibleError, msg if @raise_on_error
579
+ StringPattern.log_message(msg)
575
580
  return ""
576
581
  end
577
582
  return string
583
+ ensure
584
+ srand(saved_rng) if saved_rng
578
585
  end
579
586
  end
@@ -54,7 +54,7 @@ class StringPattern
54
54
  max_length = patt.max_length.clone
55
55
  symbol_type = patt.symbol_type.clone
56
56
  else
57
- puts "String pattern class not supported (#{pat.class} for #{pat})"
57
+ StringPattern.log_message("String pattern class not supported (#{pat.class} for #{pat})")
58
58
  return false
59
59
  end
60
60
 
@@ -133,7 +133,7 @@ class StringPattern
133
133
  required_chars << rd if rd.size == 1
134
134
  }
135
135
  if (required_chars.flatten & excluded_data.flatten).size > 0
136
- 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}"
136
+ StringPattern.log_message("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}")
137
137
  return ""
138
138
  end
139
139
  end
@@ -183,20 +183,7 @@ class StringPattern
183
183
  end
184
184
  }
185
185
  else #symbol_type=="@"
186
- string = text_to_validate
187
- wrong = %w(.. __ -- ._ _. .- -. _- -_ @. @_ @- .@ _@ -@ @@)
188
- if !(Regexp.union(*wrong) === string) #don't include any or the wrong strings
189
- if string.index("@").to_i > 0 and
190
- string[0..(string.index("@") - 1)].scan(/([a-z0-9]+([\+\._\-][a-z0-9]|)*)/i).join == string[0..(string.index("@") - 1)] and
191
- string[(string.index("@") + 1)..-1].scan(/([0-9a-z]+([\.-][a-z0-9]|)*)/i).join == string[string[(string.index("@") + 1)..-1]]
192
- error_regular_expression = false
193
- else
194
- error_regular_expression = true
195
- end
196
- else
197
- error_regular_expression = true
198
- end
199
-
186
+ error_regular_expression = !StringPattern.valid_email?(text_to_validate)
200
187
  if error_regular_expression
201
188
  detected_errors.push(:value)
202
189
  end
@@ -1,6 +1,7 @@
1
1
  SP_ADD_TO_RUBY = true if !defined?(SP_ADD_TO_RUBY)
2
2
  require_relative "string/pattern/add_to_ruby" if SP_ADD_TO_RUBY
3
3
  require_relative "string/pattern/analyze"
4
+ require_relative "string/pattern/email"
4
5
  require_relative "string/pattern/generate"
5
6
  require_relative "string/pattern/validate"
6
7
 
@@ -25,9 +26,27 @@ require_relative "string/pattern/validate"
25
26
  # In case using regular expressions the maximum when using * or + for repetitions
26
27
  # word_separator: (String, default: '_')
27
28
  # When generating words using symbol types 'w' or 'p' the character to separate the english or spanish words.
29
+ # block_list: (Array, default: empty)
30
+ # Array of words to be avoided from resultant strings.
31
+ # block_list_enabled: (TrueFalse, default: false)
32
+ # If true block_list will be take in consideration
33
+ #
34
+ # @example Generate a string
35
+ # StringPattern.generate("10:N") # => "3448910834"
36
+ # @example Validate and get errors
37
+ # StringPattern.validate(text: "ab", pattern: "6:N") # => [:min_length, :length]
28
38
  class StringPattern
39
+ # Raised when an invalid pattern is used and {raise_on_error} is true.
40
+ InvalidPatternError = Class.new(StandardError)
41
+ # Raised when generation is impossible (e.g. exhausted by dont_repeat) and {raise_on_error} is true.
42
+ GenerationImpossibleError = Class.new(StandardError)
43
+
29
44
  class << self
30
- attr_accessor :national_chars, :optimistic, :dont_repeat, :cache, :cache_values, :default_infinite, :word_separator
45
+ # @return [String, nil] When set, warning messages are sent here instead of +puts+.
46
+ attr_accessor :logger
47
+ # @return [Boolean] When true, invalid patterns or impossible generation raise instead of returning "".
48
+ attr_accessor :raise_on_error
49
+ attr_accessor :national_chars, :optimistic, :dont_repeat, :cache, :cache_values, :default_infinite, :word_separator, :block_list, :block_list_enabled
31
50
  end
32
51
  @national_chars = (("a".."z").to_a + ("A".."Z").to_a).join
33
52
  @optimistic = true
@@ -36,6 +55,10 @@ class StringPattern
36
55
  @dont_repeat = false
37
56
  @default_infinite = 10
38
57
  @word_separator = "_"
58
+ @block_list_enabled = false
59
+ @block_list = []
60
+ @logger = nil
61
+ @raise_on_error = false
39
62
  NUMBER_SET = ("0".."9").to_a
40
63
  SPECIAL_SET = [" ", "~", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "-", "_", "+", "=", "{", "}", "[", "]", "'", ";", ":", "?", ">", "<", "`", "|", "/", '"']
41
64
  ALPHA_SET_LOWER = ("a".."z").to_a
@@ -56,4 +79,61 @@ class StringPattern
56
79
  @cache = Hash.new()
57
80
  @national_chars = par
58
81
  end
82
+
83
+ def self.log_message(message)
84
+ if @logger
85
+ @logger.warn(message)
86
+ else
87
+ puts message
88
+ end
89
+ end
90
+
91
+ # Returns true if +text+ matches +pattern+; false otherwise. Uses validate under the hood.
92
+ # @param text [String] (synonyms: text_to_validate, validate)
93
+ # @param pattern [String, Symbol, Array]
94
+ # @return [Boolean]
95
+ def self.valid?(text: nil, pattern: nil, **synonyms)
96
+ text = synonyms[:text_to_validate] if text.nil? && synonyms.key?(:text_to_validate)
97
+ text = synonyms[:validate] if text.nil? && synonyms.key?(:validate)
98
+ return false if text.nil? || pattern.nil?
99
+ result = validate(text: text, pattern: pattern, **synonyms)
100
+ pattern.is_a?(Array) ? result == true : result.is_a?(Array) && result.empty?
101
+ end
102
+
103
+ # Generates up to +n+ distinct strings for +pattern+. Uses a temporary dont_repeat state.
104
+ # @param pattern [String, Symbol, Array, Regexp]
105
+ # @param n [Integer]
106
+ # @return [Array<String>]
107
+ def self.sample(pattern, n)
108
+ return [] if n <= 0
109
+ old_dont = @dont_repeat
110
+ old_cache = @cache_values.dup
111
+ @dont_repeat = true
112
+ @cache_values = {}
113
+ results = []
114
+ n.times do
115
+ s = generate(pattern)
116
+ break if s.nil? || s.empty?
117
+ results << s
118
+ end
119
+ results
120
+ ensure
121
+ @dont_repeat = old_dont
122
+ @cache_values = old_cache
123
+ end
124
+
125
+ # Generates a random UUID v4 (e.g. "550e8400-e29b-41d4-a716-446655440000").
126
+ # @return [String]
127
+ def self.uuid
128
+ require "securerandom"
129
+ SecureRandom.uuid
130
+ end
131
+
132
+ # Returns true if +str+ is a valid UUID v4 format (8-4-4-4-12 hex with version and variant bits).
133
+ # @param str [String]
134
+ # @return [Boolean]
135
+ def self.valid_uuid?(str)
136
+ return false unless str.is_a?(String)
137
+ str.match?(/\A[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\z/i)
138
+ end
59
139
  end
metadata CHANGED
@@ -1,35 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: string_pattern
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.3
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mario Ruiz
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-01-14 00:00:00.000000000 Z
11
+ date: 2026-02-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: regexp_parser
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: 1.3.0
20
17
  - - "~>"
21
18
  - !ruby/object:Gem::Version
22
- version: '1.3'
19
+ version: '2.5'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 2.5.0
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
- - - ">="
28
- - !ruby/object:Gem::Version
29
- version: 1.3.0
30
27
  - - "~>"
31
28
  - !ruby/object:Gem::Version
32
- version: '1.3'
29
+ version: '2.5'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 2.5.0
33
33
  description: 'Easily generate strings supplying a very simple pattern. ''10-20:Xn/x/''.generate
34
34
  #>qBstvc6JN8ra. Generate random strings using a regular expression (Regexp): /[a-z0-9]{2,5}w+/.gen
35
35
  . Also generate words in English or Spanish. Perfect to be used in test data factories.
@@ -61,6 +61,7 @@ files:
61
61
  - data/spanish/palabras9.json
62
62
  - lib/string/pattern/add_to_ruby.rb
63
63
  - lib/string/pattern/analyze.rb
64
+ - lib/string/pattern/email.rb
64
65
  - lib/string/pattern/generate.rb
65
66
  - lib/string/pattern/validate.rb
66
67
  - lib/string_pattern.rb
@@ -68,7 +69,7 @@ homepage: https://github.com/MarioRuiz/string_pattern
68
69
  licenses:
69
70
  - MIT
70
71
  metadata: {}
71
- post_install_message:
72
+ post_install_message:
72
73
  rdoc_options: []
73
74
  require_paths:
74
75
  - lib
@@ -83,8 +84,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
83
84
  - !ruby/object:Gem::Version
84
85
  version: '0'
85
86
  requirements: []
86
- rubygems_version: 3.0.3
87
- signing_key:
87
+ rubygems_version: 3.4.19
88
+ signing_key:
88
89
  specification_version: 4
89
90
  summary: 'Generate easily random strings following a simple pattern or regular expression.
90
91
  ''10-20:Xn/x/''.generate #>qBstvc6JN8ra. Also generate words in English or Spanish.