string_pattern 2.3.0 → 2.5.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/CHANGELOG.md +48 -0
- data/README.md +116 -6
- data/lib/string/pattern/add_to_ruby.rb +12 -6
- data/lib/string/pattern/analyze.rb +5 -5
- data/lib/string/pattern/checksum.rb +106 -0
- data/lib/string/pattern/email.rb +21 -0
- data/lib/string/pattern/generate.rb +135 -147
- data/lib/string/pattern/validate.rb +102 -27
- data/lib/string_pattern/version.rb +5 -0
- data/lib/string_pattern.rb +153 -1
- metadata +9 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8769f150212e86d1c798377384ec17edccc22ba4c09d4b15d75bdac5ea1772b0
|
|
4
|
+
data.tar.gz: '0249dda55facee2d54e3a9229ef3bfb5e587eeac6adc72849c6a374fa15eb9dd'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a6247d3b5b23256e06bdd1a4079197f238b856fd8f492878bfca79373abd257cf7b5b6002a6bcf363fc21ce5402222fc7c666002c0c2bb05ae429d4f9b9ce8bf
|
|
7
|
+
data.tar.gz: e72ce0732bb53fcc97b96623d43b76d73b774c2c3033c9ef4143edb53935120d734d4cbb7677e3db26cb0504047c2827576a3dbf068894f47a7092d1c9e586c2
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
|
+
|
|
7
|
+
## [2.5.0] - 2026-09-23
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Added `StringPattern.with` to apply configuration for the duration of a block, and `StringPattern.reset!` to restore defaults and clear caches.
|
|
11
|
+
- Added `StringPattern::VERSION`.
|
|
12
|
+
- Added full-string `Regexp` validation via `StringPattern.validate`, `StringPattern.valid?`, and `Regexp#validate` / `#val`.
|
|
13
|
+
- Added `StringPattern.explain` with the failing segment index for pattern arrays.
|
|
14
|
+
- Added `StringPattern.iban` / `valid_iban?` (ISO 13616) and `StringPattern.luhn` / `valid_luhn?`.
|
|
15
|
+
- Added `block_list_regexp` to opt into the previous regular-expression block list.
|
|
16
|
+
- `StringPattern.sample` forwards `seed:` and other generate options.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- Generation uses its own `Random` instance. `seed:` no longer calls `srand`, so it does not change `Kernel#rand`. Sequences for a given seed may differ from 2.4.0.
|
|
20
|
+
- `block_list` matches literals (case-insensitive) unless `block_list_regexp` is true.
|
|
21
|
+
- Spanish word generation loads all `data/spanish/palabras*.json` files instead of one random file.
|
|
22
|
+
- Invalid patterns and impossible generation honor `raise_on_error` on every failure path.
|
|
23
|
+
- Required Ruby version is 3.0 or newer.
|
|
24
|
+
|
|
25
|
+
## [2.4.0] - 2026-02-11
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
- Added `StringPattern.valid_email?` helper and centralized email format checks.
|
|
29
|
+
- Added `StringPattern.valid?(text:, pattern:)` for boolean validation.
|
|
30
|
+
- Added `StringPattern.sample(pattern, n)` for batch generation of distinct values.
|
|
31
|
+
- Added `StringPattern.uuid` and `StringPattern.valid_uuid?`.
|
|
32
|
+
- Added support for `seed:` in generation for reproducible outputs.
|
|
33
|
+
- Added support for `block_list` as a `Proc`.
|
|
34
|
+
- Added `StringPattern::InvalidPatternError` and `StringPattern::GenerationImpossibleError`.
|
|
35
|
+
- Added `StringPattern.logger` and `StringPattern.raise_on_error` configuration.
|
|
36
|
+
- Added `spec/string/pattern/analyze_spec.rb`.
|
|
37
|
+
- Added GitHub Actions CI workflow.
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
- Fixed email-domain comparison bug in email validation/generation paths.
|
|
41
|
+
- Replaced internal direct `puts` calls with centralized logging (`StringPattern.log_message`).
|
|
42
|
+
- Updated README with analyze/validation/error-handling/new-features documentation.
|
|
43
|
+
- Updated CI target Ruby versions (3.0, 3.1, 3.2, 3.3).
|
|
44
|
+
|
|
45
|
+
## [2.3.0] - 2025-xx-xx
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
- Previous release notes not yet backfilled.
|
data/README.md
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
# StringPattern
|
|
2
2
|
|
|
3
3
|
[](https://rubygems.org/gems/string_pattern)
|
|
4
|
-
[](https://github.com/MarioRuiz/string_pattern/actions/workflows/ci.yml)
|
|
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
|
-
|
|
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.
|
|
318
322
|
|
|
319
|
-
|
|
320
|
-
|
|
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,114 @@ 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
|
+
|
|
506
|
+
Entries in `block_list` are matched as case-insensitive literals. Set `StringPattern.block_list_regexp = true` when an entry should be a regular expression, which was the behavior before 2.5.0.
|
|
507
|
+
|
|
508
|
+
#### Scoped configuration
|
|
509
|
+
|
|
510
|
+
`StringPattern.with` applies settings for one block and restores the previous values afterwards, including when the block raises:
|
|
511
|
+
|
|
512
|
+
```ruby
|
|
513
|
+
StringPattern.with(dont_repeat: true, raise_on_error: true) do
|
|
514
|
+
"6:N".gen
|
|
515
|
+
end
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
`StringPattern.reset!` restores the default configuration and clears the pattern cache and the generated-value cache.
|
|
519
|
+
|
|
520
|
+
#### Validate a regular expression
|
|
521
|
+
|
|
522
|
+
`valid?` and `validate` accept a `Regexp` and match the whole string. The same methods are available on the regexp itself:
|
|
523
|
+
|
|
524
|
+
```ruby
|
|
525
|
+
StringPattern.valid?(text: "abc", pattern: /[a-z]+/) # => true
|
|
526
|
+
StringPattern.valid?(text: "abc1", pattern: /[a-z]+/) # => false
|
|
527
|
+
/[a-z]+/.validate("abc") # => []
|
|
528
|
+
/[a-z]+/.val("abc1") # => [:value]
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
#### Explain a validation
|
|
532
|
+
|
|
533
|
+
```ruby
|
|
534
|
+
StringPattern.explain(text: "ab", pattern: "6:N")
|
|
535
|
+
# => { valid: false, errors: [:min_length, :length, :value, :string_set_not_allowed], message: "invalid: ...", index: nil }
|
|
536
|
+
|
|
537
|
+
StringPattern.explain(text: "333111", pattern: ["3:n", "3:x"])
|
|
538
|
+
# => { valid: false, errors: [...], message: "segment 1 does not match: ...", index: 1 }
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
#### IBAN and Luhn
|
|
542
|
+
|
|
543
|
+
International checksums for test data. `iban` requires a country code and only generates countries whose length is known (ES, DE, FR, GB, PT, IT, NL, BE). The BBAN is random. `valid_iban?` accepts any well-formed IBAN. `luhn` builds a digit string with a valid Luhn check digit and does not represent a real card.
|
|
544
|
+
|
|
545
|
+
```ruby
|
|
546
|
+
StringPattern.iban(country: "DE") # => "DE..."
|
|
547
|
+
StringPattern.valid_iban?("DE89 3704 0044 0532 0130 00") # => true
|
|
548
|
+
StringPattern.luhn(length: 16) # => "16 digits"
|
|
549
|
+
StringPattern.valid_luhn?("79927398713") # => true
|
|
550
|
+
```
|
|
441
551
|
|
|
442
552
|
## Contributing
|
|
443
553
|
|
|
@@ -78,6 +78,13 @@ class Regexp
|
|
|
78
78
|
|
|
79
79
|
alias gen generate
|
|
80
80
|
|
|
81
|
+
# Validates +string_to_validate+ against this regular expression as a full-string match.
|
|
82
|
+
def validate(string_to_validate, expected_errors: [], not_expected_errors: [], **synonyms)
|
|
83
|
+
StringPattern.validate(text: string_to_validate, pattern: self, expected_errors: expected_errors, not_expected_errors: not_expected_errors, **synonyms)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
alias val validate
|
|
87
|
+
|
|
81
88
|
# adds method to convert a Regexp to StringPattern
|
|
82
89
|
# returns an array of string patterns or just one string pattern
|
|
83
90
|
def to_sp
|
|
@@ -106,7 +113,7 @@ class Regexp
|
|
|
106
113
|
elsif token == :literal and text.size == 2
|
|
107
114
|
text = text[1]
|
|
108
115
|
else
|
|
109
|
-
|
|
116
|
+
StringPattern.log_message("Report token not controlled: type: #{type}, token: #{token}, text: '#{text}' [#{ts}..#{te}]")
|
|
110
117
|
end
|
|
111
118
|
end
|
|
112
119
|
|
|
@@ -165,7 +172,7 @@ class Regexp
|
|
|
165
172
|
set_negate = false
|
|
166
173
|
else
|
|
167
174
|
pats += "]"
|
|
168
|
-
end
|
|
175
|
+
end
|
|
169
176
|
|
|
170
177
|
end
|
|
171
178
|
elsif type == :group
|
|
@@ -190,7 +197,6 @@ class Regexp
|
|
|
190
197
|
patg << pats
|
|
191
198
|
pats = ""
|
|
192
199
|
elsif patg.empty?
|
|
193
|
-
# for the case the first element was not added to patg and was on pata fex: (a+|b|c)
|
|
194
200
|
patg << pata.pop
|
|
195
201
|
end
|
|
196
202
|
end
|
|
@@ -299,11 +305,11 @@ class Regexp
|
|
|
299
305
|
end
|
|
300
306
|
if pats != ""
|
|
301
307
|
if pata.empty?
|
|
302
|
-
if pats[0] == "[" and pats[-1] == "]"
|
|
308
|
+
if pats[0] == "[" and pats[-1] == "]"
|
|
303
309
|
pata = ["1:#{pats}"]
|
|
304
310
|
end
|
|
305
311
|
else
|
|
306
|
-
pata[-1] += pats[1]
|
|
312
|
+
pata[-1] += pats[1]
|
|
307
313
|
end
|
|
308
314
|
end
|
|
309
315
|
if pata.size == 1 and pata[0].kind_of?(String)
|
|
@@ -325,7 +331,7 @@ module Kernel
|
|
|
325
331
|
if pattern.is_a?(String) || pattern.is_a?(Array) || pattern.is_a?(Symbol) || pattern.is_a?(Regexp)
|
|
326
332
|
StringPattern.generate(pattern, expected_errors: expected_errors, **synonyms)
|
|
327
333
|
else
|
|
328
|
-
|
|
334
|
+
StringPattern.log_message(" Kernel generate method: class not recognized:#{pattern.class}")
|
|
329
335
|
end
|
|
330
336
|
end
|
|
331
337
|
|
|
@@ -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,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class StringPattern
|
|
4
|
+
# IBAN lengths we can generate. Validation accepts any well-formed IBAN.
|
|
5
|
+
IBAN_LENGTHS = {
|
|
6
|
+
"ES" => 24,
|
|
7
|
+
"DE" => 22,
|
|
8
|
+
"FR" => 27,
|
|
9
|
+
"GB" => 22,
|
|
10
|
+
"PT" => 25,
|
|
11
|
+
"IT" => 27,
|
|
12
|
+
"NL" => 18,
|
|
13
|
+
"BE" => 16
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
# Generates a random IBAN for +country+ (ISO 13616). The BBAN is random, not a real bank account.
|
|
17
|
+
# @param country [String, Symbol] two-letter country code
|
|
18
|
+
# @return [String]
|
|
19
|
+
# @raise [ArgumentError] when the country is not in {IBAN_LENGTHS}
|
|
20
|
+
def self.iban(country:)
|
|
21
|
+
code = country.to_s.upcase
|
|
22
|
+
length = IBAN_LENGTHS[code]
|
|
23
|
+
raise ArgumentError, "unknown IBAN country: #{country.inspect}" unless length
|
|
24
|
+
|
|
25
|
+
rng = Random.new
|
|
26
|
+
alphabet = [*("0".."9"), *("A".."Z")]
|
|
27
|
+
bban = Array.new(length - 4) { alphabet.sample(random: rng) }.join
|
|
28
|
+
"#{code}#{iban_check_digits(code, bban)}#{bban}"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Returns true when +str+ is a well-formed IBAN (format and mod-97), ignoring spaces.
|
|
32
|
+
# @param str [String]
|
|
33
|
+
# @return [Boolean]
|
|
34
|
+
def self.valid_iban?(str)
|
|
35
|
+
return false unless str.is_a?(String)
|
|
36
|
+
|
|
37
|
+
compact = str.gsub(/\s+/, "").upcase
|
|
38
|
+
return false unless compact.match?(/\A[A-Z]{2}\d{2}[A-Z0-9]+\z/)
|
|
39
|
+
return false unless (15..34).cover?(compact.length)
|
|
40
|
+
|
|
41
|
+
rearranged = compact[4..] + compact[0, 4]
|
|
42
|
+
iban_mod97(iban_to_digits(rearranged)) == 1
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Generates a digit string of +length+ that satisfies the Luhn checksum.
|
|
46
|
+
# @param length [Integer] total number of digits, at least 2
|
|
47
|
+
# @return [String]
|
|
48
|
+
# @raise [ArgumentError] when length is below 2
|
|
49
|
+
def self.luhn(length:)
|
|
50
|
+
length = Integer(length)
|
|
51
|
+
raise ArgumentError, "length must be >= 2" if length < 2
|
|
52
|
+
|
|
53
|
+
rng = Random.new
|
|
54
|
+
digits = Array.new(length - 1) { rng.rand(10) }
|
|
55
|
+
digits << luhn_check_digit(digits)
|
|
56
|
+
digits.join
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Returns true when +str+ is a digit string with a valid Luhn checksum.
|
|
60
|
+
# @param str [String]
|
|
61
|
+
# @return [Boolean]
|
|
62
|
+
def self.valid_luhn?(str)
|
|
63
|
+
return false unless str.is_a?(String) && str.match?(/\A\d{2,}\z/)
|
|
64
|
+
|
|
65
|
+
luhn_sum(str.chars.map(&:to_i)) % 10 == 0
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def self.iban_check_digits(country, bban)
|
|
69
|
+
remainder = iban_mod97(iban_to_digits("#{bban}#{country}00"))
|
|
70
|
+
format("%02d", 98 - remainder)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def self.iban_to_digits(value)
|
|
74
|
+
value.each_char.map { |char|
|
|
75
|
+
if char >= "A" && char <= "Z"
|
|
76
|
+
(char.ord - 55).to_s
|
|
77
|
+
else
|
|
78
|
+
char
|
|
79
|
+
end
|
|
80
|
+
}.join
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def self.iban_mod97(digit_string)
|
|
84
|
+
remainder = 0
|
|
85
|
+
digit_string.scan(/.{1,9}/).each do |chunk|
|
|
86
|
+
remainder = "#{remainder}#{chunk}".to_i % 97
|
|
87
|
+
end
|
|
88
|
+
remainder
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def self.luhn_check_digit(payload)
|
|
92
|
+
(10 - (luhn_sum(payload + [0]) % 10)) % 10
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def self.luhn_sum(digits)
|
|
96
|
+
digits.reverse.each_with_index.sum do |digit, index|
|
|
97
|
+
value = digit.to_i
|
|
98
|
+
if index.odd?
|
|
99
|
+
value *= 2
|
|
100
|
+
value -= 9 if value > 9
|
|
101
|
+
end
|
|
102
|
+
value
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
private_class_method :iban_check_digits, :iban_to_digits, :iban_mod97, :luhn_check_digit, :luhn_sum
|
|
106
|
+
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
|