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 +4 -4
- data/README.md +81 -5
- data/lib/string/pattern/add_to_ruby.rb +47 -18
- data/lib/string/pattern/analyze.rb +5 -5
- data/lib/string/pattern/email.rb +21 -0
- data/lib/string/pattern/generate.rb +33 -26
- data/lib/string/pattern/validate.rb +3 -16
- data/lib/string_pattern.rb +81 -1
- metadata +15 -14
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 47172de723c22b8175a1119aa51c065056ec4eab029cfb989b4fa4c9bc20d845
|
|
4
|
+
data.tar.gz: c9e9996c3f5ec59813b157a9f818c10723fb1b9f95a1ed871ba80219d04bbe6e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 840a84447c523a01e21657fa772c190388a1401e41d254e5340fe9bd081eb0740ba200161c0a29e06dcfa841632c26d51346da53bf2c5a8f93ad3459aa455930
|
|
7
|
+
data.tar.gz: f53b2b1ef110f8e20b95e7e5bafac00067bbc782e2817fa3292cbd4b60492454b171133b80b8808e737606c83dd8f2df59d5d57447bcc5cc225d6239c0edb7e3
|
data/README.md
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
[](https://rubygems.org/gems/string_pattern)
|
|
4
4
|
[](https://github.com/MarioRuiz/string_pattern)
|
|
5
5
|
[](https://coveralls.io/github/MarioRuiz/string_pattern?branch=master)
|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
132
|
+
pata << pats.chop
|
|
130
133
|
else
|
|
131
|
-
pata << "1:[#{pats}"
|
|
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
|
-
|
|
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
|
-
|
|
233
|
+
if options.include?('i')
|
|
234
|
+
pats = "L" + pats
|
|
235
|
+
else
|
|
236
|
+
pats = "x" + pats
|
|
237
|
+
end
|
|
220
238
|
elsif range == "A-Z"
|
|
221
|
-
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
|
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] == "]"
|
|
301
|
+
if pats[0] == "[" and pats[-1] == "]"
|
|
273
302
|
pata = ["1:#{pats}"]
|
|
274
303
|
end
|
|
275
304
|
else
|
|
276
|
-
pata[-1] += pats[1]
|
|
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
|
-
|
|
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
|
-
#
|
|
4
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
574
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
data/lib/string_pattern.rb
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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:
|
|
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: '
|
|
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: '
|
|
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.
|
|
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.
|