string_pattern 2.3.0 → 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: 7b9ea7310d3fb561b37d8b0753360ddd5742188a2f71b68560a121d716d65083
4
- data.tar.gz: a0504e61ff66a749d7c0b0fcc36ad629474d793abd78bc0456bdd6a9ddeae7f7
3
+ metadata.gz: 47172de723c22b8175a1119aa51c065056ec4eab029cfb989b4fa4c9bc20d845
4
+ data.tar.gz: c9e9996c3f5ec59813b157a9f818c10723fb1b9f95a1ed871ba80219d04bbe6e
5
5
  SHA512:
6
- metadata.gz: 3ade8ead8f7434fb1893f62e44523245fb52776a24ffc9f9f548a16f790ec035f2411d223203886571decd7c7455b6f56cf41a7e62c491910762c210754e11f7
7
- data.tar.gz: bbb68322396f6017f6f6f1b6190b1e14cc0a3cea804efa66f6527f892be032fe5cebc321e39ea399e0af9f69ee6f5e4ef1c7a9241ac97541cf6d4aac3191c658
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
 
@@ -438,6 +440,69 @@ StringPattern.block_list_enabled = true
438
440
  "2-20:Tn".gen #>AAñ34Ef99éNOP
439
441
  ```
440
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
+
441
506
 
442
507
  ## Contributing
443
508
 
@@ -106,7 +106,7 @@ class Regexp
106
106
  elsif token == :literal and text.size == 2
107
107
  text = text[1]
108
108
  else
109
- 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}]")
110
110
  end
111
111
  end
112
112
 
@@ -165,7 +165,7 @@ class Regexp
165
165
  set_negate = false
166
166
  else
167
167
  pats += "]"
168
- end
168
+ end
169
169
 
170
170
  end
171
171
  elsif type == :group
@@ -190,7 +190,6 @@ class Regexp
190
190
  patg << pats
191
191
  pats = ""
192
192
  elsif patg.empty?
193
- # for the case the first element was not added to patg and was on pata fex: (a+|b|c)
194
193
  patg << pata.pop
195
194
  end
196
195
  end
@@ -299,11 +298,11 @@ class Regexp
299
298
  end
300
299
  if pats != ""
301
300
  if pata.empty?
302
- if pats[0] == "[" and pats[-1] == "]" #fex: /[12ab]/
301
+ if pats[0] == "[" and pats[-1] == "]"
303
302
  pata = ["1:#{pats}"]
304
303
  end
305
304
  else
306
- pata[-1] += pats[1] #fex: /allo/
305
+ pata[-1] += pats[1]
307
306
  end
308
307
  end
309
308
  if pata.size == 1 and pata[0].kind_of?(String)
@@ -325,7 +324,7 @@ module Kernel
325
324
  if pattern.is_a?(String) || pattern.is_a?(Array) || pattern.is_a?(Symbol) || pattern.is_a?(Regexp)
326
325
  StringPattern.generate(pattern, expected_errors: expected_errors, **synonyms)
327
326
  else
328
- puts " Kernel generate method: class not recognized:#{pattern.class}"
327
+ StringPattern.log_message(" Kernel generate method: class not recognized:#{pattern.class}")
329
328
  end
330
329
  end
331
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
@@ -569,7 +560,9 @@ class StringPattern
569
560
  end
570
561
  end
571
562
  if @block_list_enabled
572
- if @block_list.is_a?(Array)
563
+ if @block_list.respond_to?(:call)
564
+ good_result = false if @block_list.call(string)
565
+ elsif @block_list.is_a?(Array)
573
566
  @block_list.each do |bl|
574
567
  if string.match?(/#{bl}/i)
575
568
  good_result = false
@@ -580,10 +573,14 @@ class StringPattern
580
573
  end
581
574
  end until good_result or tries > 10000
582
575
  unless good_result
583
- puts "Not possible to generate the string on StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}"
584
- 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)
585
580
  return ""
586
581
  end
587
582
  return string
583
+ ensure
584
+ srand(saved_rng) if saved_rng
588
585
  end
589
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
 
@@ -29,8 +30,22 @@ require_relative "string/pattern/validate"
29
30
  # Array of words to be avoided from resultant strings.
30
31
  # block_list_enabled: (TrueFalse, default: false)
31
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]
32
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
+
33
44
  class << self
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
34
49
  attr_accessor :national_chars, :optimistic, :dont_repeat, :cache, :cache_values, :default_infinite, :word_separator, :block_list, :block_list_enabled
35
50
  end
36
51
  @national_chars = (("a".."z").to_a + ("A".."Z").to_a).join
@@ -42,6 +57,8 @@ class StringPattern
42
57
  @word_separator = "_"
43
58
  @block_list_enabled = false
44
59
  @block_list = []
60
+ @logger = nil
61
+ @raise_on_error = false
45
62
  NUMBER_SET = ("0".."9").to_a
46
63
  SPECIAL_SET = [" ", "~", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "-", "_", "+", "=", "{", "}", "[", "]", "'", ";", ":", "?", ">", "<", "`", "|", "/", '"']
47
64
  ALPHA_SET_LOWER = ("a".."z").to_a
@@ -63,4 +80,60 @@ class StringPattern
63
80
  @national_chars = par
64
81
  end
65
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
66
139
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: string_pattern
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mario Ruiz
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-09-20 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
@@ -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
@@ -83,7 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
83
84
  - !ruby/object:Gem::Version
84
85
  version: '0'
85
86
  requirements: []
86
- rubygems_version: 3.2.15
87
+ rubygems_version: 3.4.19
87
88
  signing_key:
88
89
  specification_version: 4
89
90
  summary: 'Generate easily random strings following a simple pattern or regular expression.