string_wizard 0.1.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 +7 -0
- data/lib/string_enhancer/core_ext.rb +156 -0
- data/lib/string_enhancer/encryption.rb +67 -0
- data/lib/string_enhancer/pattern_matcher.rb +41 -0
- data/lib/string_enhancer/transformer.rb +76 -0
- data/lib/string_enhancer/validator.rb +45 -0
- data/lib/string_enhancer/version.rb +3 -0
- data/lib/string_wizard/version.rb +3 -0
- data/lib/string_wizard.rb +261 -0
- metadata +136 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 77a377b78f738c3a61b4b851407971dca945528bf2b6107e741219c91ff126c4
|
4
|
+
data.tar.gz: 6bb673e6a74c0996fe1f5432bd36c7315f3960ac2139f4d6b113c920e028bd6f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: aef54bddf1d3424e24d9901dedbe651325c12dcaffd53fb2d86d2b2cb8af48e68303d6152364459258c12565c081079864c54cb10abf8212f5ab4979717d0659
|
7
|
+
data.tar.gz: 8dd77cf902dc0f192904c8558eb27cfe5bbd5c34465f637b1feb6839d06d02f324334d8b285cf65650f749659d0e04fccb47265dee2b4f6e3eaf36684e5b15cb
|
@@ -0,0 +1,156 @@
|
|
1
|
+
class String
|
2
|
+
# Returns a string with the first letter of each word capitalized
|
3
|
+
def titleize
|
4
|
+
StringEnhancer.titleize(self)
|
5
|
+
end
|
6
|
+
|
7
|
+
# Returns a string with all vowels removed
|
8
|
+
def remove_vowels
|
9
|
+
StringEnhancer.remove_vowels(self)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Returns a string with all consonants removed
|
13
|
+
def remove_consonants
|
14
|
+
StringEnhancer.remove_consonants(self)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns true if the string is a palindrome
|
18
|
+
def palindrome?
|
19
|
+
StringEnhancer.palindrome?(self)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns a string with alternating case
|
23
|
+
def alternating_case
|
24
|
+
StringEnhancer.alternating_case(self)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns a string with words in reverse order
|
28
|
+
def reverse_words
|
29
|
+
StringEnhancer.reverse_words(self)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Advanced string analysis
|
33
|
+
def analyze
|
34
|
+
StringEnhancer.analyze(self)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Pattern matching
|
38
|
+
def match_pattern(pattern)
|
39
|
+
StringEnhancer.match_pattern(self, pattern)
|
40
|
+
end
|
41
|
+
|
42
|
+
def matches_pattern?(pattern_name)
|
43
|
+
StringEnhancer::PatternMatcher.match_pattern(self, pattern_name)
|
44
|
+
end
|
45
|
+
|
46
|
+
def extract_patterns
|
47
|
+
StringEnhancer::PatternMatcher.extract_patterns(self)
|
48
|
+
end
|
49
|
+
|
50
|
+
def validate_format(pattern_name)
|
51
|
+
StringEnhancer::PatternMatcher.validate_format(self, pattern_name)
|
52
|
+
end
|
53
|
+
|
54
|
+
def sanitize(pattern_name)
|
55
|
+
StringEnhancer::PatternMatcher.sanitize(self, pattern_name)
|
56
|
+
end
|
57
|
+
|
58
|
+
# String transformation chain
|
59
|
+
def transform
|
60
|
+
StringEnhancer::Transformer.transform(self)
|
61
|
+
end
|
62
|
+
|
63
|
+
# String similarity
|
64
|
+
def similarity(other)
|
65
|
+
StringEnhancer.similarity(self, other)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Parallel processing
|
69
|
+
def self.process_batch(strings, batch_size: 100, &block)
|
70
|
+
StringEnhancer.process_batch(strings, batch_size: batch_size, &block)
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.analyze_batch(strings, batch_size: 100)
|
74
|
+
StringEnhancer.analyze_batch(strings, batch_size: batch_size)
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.transform_batch(strings, transformations, batch_size: 100)
|
78
|
+
StringEnhancer.transform_batch(strings, transformations, batch_size: batch_size)
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.similarity_matrix(strings, batch_size: 100)
|
82
|
+
StringEnhancer.similarity_matrix(strings, batch_size: batch_size)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Encryption
|
86
|
+
def encrypt(key, algorithm: 'AES-256-CBC')
|
87
|
+
StringEnhancer.encrypt(self, key, algorithm: algorithm)
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.decrypt(encrypted_str, key, algorithm: 'AES-256-CBC')
|
91
|
+
StringEnhancer.decrypt(encrypted_str, key, algorithm: algorithm)
|
92
|
+
end
|
93
|
+
|
94
|
+
def hash(algorithm: :sha256)
|
95
|
+
StringEnhancer.hash(self, algorithm: algorithm)
|
96
|
+
end
|
97
|
+
|
98
|
+
def secure_compare(other)
|
99
|
+
StringEnhancer.secure_compare(self, other)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Fuzzy matching
|
103
|
+
def fuzzy_match(candidates, threshold: 0.8)
|
104
|
+
StringEnhancer.fuzzy_match(self, candidates, threshold: threshold)
|
105
|
+
end
|
106
|
+
|
107
|
+
def best_match(candidates, threshold: 0.8)
|
108
|
+
StringEnhancer.best_match(self, candidates, threshold: threshold)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Validation methods
|
112
|
+
def valid_email?
|
113
|
+
StringEnhancer.valid_email?(self)
|
114
|
+
end
|
115
|
+
|
116
|
+
def valid_url?
|
117
|
+
StringEnhancer.valid_url?(self)
|
118
|
+
end
|
119
|
+
|
120
|
+
def valid_phone?
|
121
|
+
StringEnhancer.valid_phone?(self)
|
122
|
+
end
|
123
|
+
|
124
|
+
def valid_date?(format: '%Y-%m-%d')
|
125
|
+
StringEnhancer.valid_date?(self, format: format)
|
126
|
+
end
|
127
|
+
|
128
|
+
def valid_time?
|
129
|
+
StringEnhancer.valid_time?(self)
|
130
|
+
end
|
131
|
+
|
132
|
+
def valid_ip?
|
133
|
+
StringEnhancer.valid_ip?(self)
|
134
|
+
end
|
135
|
+
|
136
|
+
def valid_credit_card?
|
137
|
+
StringEnhancer.valid_credit_card?(self)
|
138
|
+
end
|
139
|
+
|
140
|
+
def valid_hex_color?
|
141
|
+
StringEnhancer.valid_hex_color?(self)
|
142
|
+
end
|
143
|
+
|
144
|
+
def valid_json?
|
145
|
+
StringEnhancer.valid_json?(self)
|
146
|
+
end
|
147
|
+
|
148
|
+
def valid_xml?
|
149
|
+
StringEnhancer.valid_xml?(self)
|
150
|
+
end
|
151
|
+
|
152
|
+
# Clear memoization cache
|
153
|
+
def self.clear_cache
|
154
|
+
StringEnhancer.clear_cache
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module StringEnhancer
|
2
|
+
module Encryption
|
3
|
+
require 'openssl'
|
4
|
+
require 'base64'
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def encrypt(str, key, algorithm: 'AES-256-CBC')
|
8
|
+
cipher = OpenSSL::Cipher.new(algorithm)
|
9
|
+
cipher.encrypt
|
10
|
+
cipher.key = Digest::SHA256.digest(key)
|
11
|
+
iv = cipher.random_iv
|
12
|
+
|
13
|
+
encrypted = cipher.update(str) + cipher.final
|
14
|
+
Base64.strict_encode64(iv + encrypted)
|
15
|
+
end
|
16
|
+
|
17
|
+
def decrypt(encrypted_str, key, algorithm: 'AES-256-CBC')
|
18
|
+
decoded = Base64.strict_decode64(encrypted_str)
|
19
|
+
cipher = OpenSSL::Cipher.new(algorithm)
|
20
|
+
cipher.decrypt
|
21
|
+
cipher.key = Digest::SHA256.digest(key)
|
22
|
+
|
23
|
+
iv = decoded[0...cipher.iv_len]
|
24
|
+
encrypted = decoded[cipher.iv_len..-1]
|
25
|
+
|
26
|
+
cipher.iv = iv
|
27
|
+
cipher.update(encrypted) + cipher.final
|
28
|
+
end
|
29
|
+
|
30
|
+
def hash(str, algorithm: :sha256)
|
31
|
+
case algorithm
|
32
|
+
when :sha256
|
33
|
+
Digest::SHA256.hexdigest(str)
|
34
|
+
when :sha512
|
35
|
+
Digest::SHA512.hexdigest(str)
|
36
|
+
when :md5
|
37
|
+
Digest::MD5.hexdigest(str)
|
38
|
+
else
|
39
|
+
raise Error, "Unsupported hash algorithm: #{algorithm}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def secure_compare(str1, str2)
|
44
|
+
return false unless str1.bytesize == str2.bytesize
|
45
|
+
|
46
|
+
result = 0
|
47
|
+
str1.bytes.zip(str2.bytes) do |b1, b2|
|
48
|
+
result |= b1 ^ b2
|
49
|
+
end
|
50
|
+
result.zero?
|
51
|
+
end
|
52
|
+
|
53
|
+
def generate_salt(length = 16)
|
54
|
+
OpenSSL::Random.random_bytes(length)
|
55
|
+
end
|
56
|
+
|
57
|
+
def pbkdf2(str, salt, iterations: 10000, key_length: 32)
|
58
|
+
OpenSSL::PKCS5.pbkdf2_hmac_sha1(
|
59
|
+
str,
|
60
|
+
salt,
|
61
|
+
iterations,
|
62
|
+
key_length
|
63
|
+
)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module StringEnhancer
|
2
|
+
module PatternMatcher
|
3
|
+
# Predefined patterns for common use cases
|
4
|
+
PATTERNS = {
|
5
|
+
email: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i,
|
6
|
+
url: %r{\Ahttps?://[^\s/$.?#].[^\s]*\z},
|
7
|
+
phone: /\A\+?[\d\s\-()]{10,}\z/,
|
8
|
+
date: /\A\d{4}-\d{2}-\d{2}\z/,
|
9
|
+
time: /\A([01]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?\z/,
|
10
|
+
ip_address: /\A(\d{1,3}\.){3}\d{1,3}\z/,
|
11
|
+
credit_card: /\A\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\z/,
|
12
|
+
hex_color: /\A#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})\z/
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
def self.match_pattern(str, pattern_name)
|
16
|
+
pattern = PATTERNS[pattern_name.to_sym]
|
17
|
+
raise PatternMatchError, "Unknown pattern: #{pattern_name}" unless pattern
|
18
|
+
|
19
|
+
pattern.match?(str)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.extract_patterns(str)
|
23
|
+
matches = {}
|
24
|
+
PATTERNS.each do |name, pattern|
|
25
|
+
matches[name] = str.scan(pattern)
|
26
|
+
end
|
27
|
+
matches
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.validate_format(str, pattern_name)
|
31
|
+
match_pattern(str, pattern_name)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.sanitize(str, pattern_name)
|
35
|
+
pattern = PATTERNS[pattern_name.to_sym]
|
36
|
+
raise PatternMatchError, "Unknown pattern: #{pattern_name}" unless pattern
|
37
|
+
|
38
|
+
str.gsub(pattern, '')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module StringEnhancer
|
2
|
+
module Transformer
|
3
|
+
class Chain
|
4
|
+
def initialize(str)
|
5
|
+
@str = str
|
6
|
+
@transformations = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def titleize
|
10
|
+
@transformations << :titleize
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
def upcase
|
15
|
+
@transformations << :upcase
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
def downcase
|
20
|
+
@transformations << :downcase
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def reverse
|
25
|
+
@transformations << :reverse
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def strip
|
30
|
+
@transformations << :strip
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def remove_vowels
|
35
|
+
@transformations << :remove_vowels
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def remove_consonants
|
40
|
+
@transformations << :remove_consonants
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def alternating_case
|
45
|
+
@transformations << :alternating_case
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def reverse_words
|
50
|
+
@transformations << :reverse_words
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
def custom(&block)
|
55
|
+
@transformations << block
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
def apply
|
60
|
+
result = @str.dup
|
61
|
+
@transformations.each do |transformation|
|
62
|
+
if transformation.is_a?(Proc)
|
63
|
+
result = transformation.call(result)
|
64
|
+
else
|
65
|
+
result = StringEnhancer.transform(result, transformation)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
result
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.transform(str)
|
73
|
+
Chain.new(str)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module StringEnhancer
|
2
|
+
module Validator
|
3
|
+
class << self
|
4
|
+
def valid_email?(str)
|
5
|
+
str.match?(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i)
|
6
|
+
end
|
7
|
+
|
8
|
+
def valid_url?(str)
|
9
|
+
str.match?(%r{\Ahttps?://[^\s/$.?#].[^\s]*\z})
|
10
|
+
end
|
11
|
+
|
12
|
+
def valid_phone?(str)
|
13
|
+
str.match?(/\A\+?[\d\s\-()]{10,}\z/)
|
14
|
+
end
|
15
|
+
|
16
|
+
def valid_date?(str, format: '%Y-%m-%d')
|
17
|
+
Date.strptime(str, format) rescue false
|
18
|
+
end
|
19
|
+
|
20
|
+
def valid_time?(str)
|
21
|
+
str.match?(/\A([01]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?\z/)
|
22
|
+
end
|
23
|
+
|
24
|
+
def valid_ip?(str)
|
25
|
+
str.match?(/\A(\d{1,3}\.){3}\d{1,3}\z/)
|
26
|
+
end
|
27
|
+
|
28
|
+
def valid_credit_card?(str)
|
29
|
+
str.match?(/\A\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\z/)
|
30
|
+
end
|
31
|
+
|
32
|
+
def valid_hex_color?(str)
|
33
|
+
str.match?(/\A#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})\z/)
|
34
|
+
end
|
35
|
+
|
36
|
+
def valid_json?(str)
|
37
|
+
JSON.parse(str) rescue false
|
38
|
+
end
|
39
|
+
|
40
|
+
def valid_xml?(str)
|
41
|
+
Nokogiri::XML(str) rescue false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,261 @@
|
|
1
|
+
require "string_wizard/version"
|
2
|
+
require "string_wizard/core_ext"
|
3
|
+
require "string_wizard/pattern_matcher"
|
4
|
+
require "string_wizard/transformer"
|
5
|
+
require "string_wizard/validator"
|
6
|
+
require "json"
|
7
|
+
require "nokogiri"
|
8
|
+
require "date"
|
9
|
+
|
10
|
+
module StringWizard
|
11
|
+
class Error < StandardError; end
|
12
|
+
class PatternMatchError < Error; end
|
13
|
+
class EncryptionError < Error; end
|
14
|
+
|
15
|
+
# Memoization cache for expensive operations
|
16
|
+
@memo_cache = {}
|
17
|
+
|
18
|
+
class << self
|
19
|
+
# Basic string operations
|
20
|
+
def titleize(str)
|
21
|
+
str.split.map(&:capitalize).join(' ')
|
22
|
+
end
|
23
|
+
|
24
|
+
def remove_vowels(str)
|
25
|
+
str.gsub(/[aeiouAEIOU]/, '')
|
26
|
+
end
|
27
|
+
|
28
|
+
def remove_consonants(str)
|
29
|
+
str.gsub(/[^aeiouAEIOU\s]/, '')
|
30
|
+
end
|
31
|
+
|
32
|
+
def palindrome?(str)
|
33
|
+
normalized = str.downcase.gsub(/[^a-z0-9]/, '')
|
34
|
+
normalized == normalized.reverse
|
35
|
+
end
|
36
|
+
|
37
|
+
def alternating_case(str)
|
38
|
+
str.chars.each_with_index.map do |char, index|
|
39
|
+
index.even? ? char.upcase : char.downcase
|
40
|
+
end.join
|
41
|
+
end
|
42
|
+
|
43
|
+
def reverse_words(str)
|
44
|
+
str.split.reverse.join(' ')
|
45
|
+
end
|
46
|
+
|
47
|
+
# Advanced string analysis
|
48
|
+
def analyze(str)
|
49
|
+
{
|
50
|
+
length: str.length,
|
51
|
+
word_count: str.split.size,
|
52
|
+
character_frequency: character_frequency(str),
|
53
|
+
vowel_count: str.scan(/[aeiouAEIOU]/).size,
|
54
|
+
consonant_count: str.scan(/[^aeiouAEIOU\s\W]/).size,
|
55
|
+
digit_count: str.scan(/\d/).size,
|
56
|
+
special_char_count: str.scan(/[^a-zA-Z0-9\s]/).size,
|
57
|
+
entropy: calculate_entropy(str),
|
58
|
+
readability_score: calculate_readability(str)
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
# Pattern matching
|
63
|
+
def match_pattern(str, pattern)
|
64
|
+
regex = pattern.is_a?(Regexp) ? pattern : Regexp.new(pattern)
|
65
|
+
match = regex.match(str)
|
66
|
+
return nil unless match
|
67
|
+
|
68
|
+
if match.named_captures.any?
|
69
|
+
match.named_captures
|
70
|
+
else
|
71
|
+
match.captures
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# String transformation chain
|
76
|
+
def transform(str, *transformations)
|
77
|
+
transformations.inject(str) do |result, transformation|
|
78
|
+
case transformation
|
79
|
+
when :titleize then titleize(result)
|
80
|
+
when :upcase then result.upcase
|
81
|
+
when :downcase then result.downcase
|
82
|
+
when :reverse then result.reverse
|
83
|
+
when :strip then result.strip
|
84
|
+
when :remove_vowels then remove_vowels(result)
|
85
|
+
when :remove_consonants then remove_consonants(result)
|
86
|
+
when :alternating_case then alternating_case(result)
|
87
|
+
when :reverse_words then reverse_words(result)
|
88
|
+
else
|
89
|
+
raise Error, "Unknown transformation: #{transformation}"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Memoized character frequency analysis
|
95
|
+
def character_frequency(str)
|
96
|
+
@memo_cache["frequency_#{str}"] ||= begin
|
97
|
+
frequency = Hash.new(0)
|
98
|
+
str.each_char { |char| frequency[char] += 1 }
|
99
|
+
frequency
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Clear memoization cache
|
104
|
+
def clear_cache
|
105
|
+
@memo_cache.clear
|
106
|
+
end
|
107
|
+
|
108
|
+
# String similarity methods
|
109
|
+
def similarity(str1, str2)
|
110
|
+
return 1.0 if str1 == str2
|
111
|
+
return 0.0 if str1.empty? || str2.empty?
|
112
|
+
|
113
|
+
# Calculate Levenshtein distance
|
114
|
+
distance = levenshtein_distance(str1, str2)
|
115
|
+
max_length = [str1.length, str2.length].max
|
116
|
+
1.0 - (distance.to_f / max_length)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Encryption
|
120
|
+
def encrypt(str, key, algorithm: 'AES-256-CBC')
|
121
|
+
Encryption.encrypt(str, key, algorithm: algorithm)
|
122
|
+
end
|
123
|
+
|
124
|
+
def decrypt(encrypted_str, key, algorithm: 'AES-256-CBC')
|
125
|
+
Encryption.decrypt(encrypted_str, key, algorithm: algorithm)
|
126
|
+
end
|
127
|
+
|
128
|
+
def hash(str, algorithm: :sha256)
|
129
|
+
Encryption.hash(str, algorithm: algorithm)
|
130
|
+
end
|
131
|
+
|
132
|
+
def secure_compare(str1, str2)
|
133
|
+
Encryption.secure_compare(str1, str2)
|
134
|
+
end
|
135
|
+
|
136
|
+
# String validation
|
137
|
+
def valid_email?(str)
|
138
|
+
str.match?(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i)
|
139
|
+
end
|
140
|
+
|
141
|
+
def valid_url?(str)
|
142
|
+
str.match?(/\Ahttps?:\/\/[\w\-]+(\.[\w\-]+)+[\/#?]?.*\z/i)
|
143
|
+
end
|
144
|
+
|
145
|
+
def valid_phone?(str)
|
146
|
+
str.match?(/\A\+?[\d\s\-()]+\z/)
|
147
|
+
end
|
148
|
+
|
149
|
+
def valid_date?(str, format: '%Y-%m-%d')
|
150
|
+
Date.strptime(str, format)
|
151
|
+
true
|
152
|
+
rescue Date::Error
|
153
|
+
false
|
154
|
+
end
|
155
|
+
|
156
|
+
def valid_time?(str)
|
157
|
+
str.match?(/\A([01]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?\z/)
|
158
|
+
end
|
159
|
+
|
160
|
+
def valid_ip?(str)
|
161
|
+
str.match?(/\A(\d{1,3}\.){3}\d{1,3}\z/) &&
|
162
|
+
str.split('.').all? { |octet| octet.to_i.between?(0, 255) }
|
163
|
+
end
|
164
|
+
|
165
|
+
def valid_credit_card?(str)
|
166
|
+
# Remove all non-digit characters
|
167
|
+
digits = str.gsub(/\D/, '').chars.map(&:to_i)
|
168
|
+
return false if digits.empty?
|
169
|
+
|
170
|
+
# Luhn algorithm
|
171
|
+
sum = 0
|
172
|
+
digits.reverse.each_with_index do |digit, index|
|
173
|
+
if index.odd?
|
174
|
+
doubled = digit * 2
|
175
|
+
sum += doubled > 9 ? doubled - 9 : doubled
|
176
|
+
else
|
177
|
+
sum += digit
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
(sum % 10).zero?
|
182
|
+
end
|
183
|
+
|
184
|
+
def valid_hex_color?(str)
|
185
|
+
str.match?(/\A#[0-9A-Fa-f]{6}\z/)
|
186
|
+
end
|
187
|
+
|
188
|
+
def valid_json?(str)
|
189
|
+
JSON.parse(str)
|
190
|
+
true
|
191
|
+
rescue JSON::ParserError
|
192
|
+
false
|
193
|
+
end
|
194
|
+
|
195
|
+
def valid_xml?(str)
|
196
|
+
Nokogiri::XML(str) { |config| config.strict }
|
197
|
+
true
|
198
|
+
rescue Nokogiri::XML::SyntaxError
|
199
|
+
false
|
200
|
+
end
|
201
|
+
|
202
|
+
private
|
203
|
+
|
204
|
+
# Levenshtein distance calculation
|
205
|
+
def levenshtein_distance(str1, str2)
|
206
|
+
m = str1.length
|
207
|
+
n = str2.length
|
208
|
+
return m if n == 0
|
209
|
+
return n if m == 0
|
210
|
+
|
211
|
+
d = Array.new(m + 1) { Array.new(n + 1) }
|
212
|
+
|
213
|
+
(0..m).each { |i| d[i][0] = i }
|
214
|
+
(0..n).each { |j| d[0][j] = j }
|
215
|
+
|
216
|
+
(1..m).each do |i|
|
217
|
+
(1..n).each do |j|
|
218
|
+
cost = str1[i - 1] == str2[j - 1] ? 0 : 1
|
219
|
+
d[i][j] = [
|
220
|
+
d[i - 1][j] + 1, # deletion
|
221
|
+
d[i][j - 1] + 1, # insertion
|
222
|
+
d[i - 1][j - 1] + cost # substitution
|
223
|
+
].min
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
d[m][n]
|
228
|
+
end
|
229
|
+
|
230
|
+
# Calculate string entropy
|
231
|
+
def calculate_entropy(str)
|
232
|
+
return 0.0 if str.empty?
|
233
|
+
|
234
|
+
frequency = character_frequency(str)
|
235
|
+
length = str.length.to_f
|
236
|
+
|
237
|
+
-frequency.values.sum do |count|
|
238
|
+
probability = count / length
|
239
|
+
probability * Math.log2(probability)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
# Calculate readability score
|
244
|
+
def calculate_readability(str)
|
245
|
+
words = str.split
|
246
|
+
sentences = str.split(/[.!?]+/)
|
247
|
+
syllables = words.sum { |word| count_syllables(word) }
|
248
|
+
|
249
|
+
return 0.0 if words.empty? || sentences.empty?
|
250
|
+
|
251
|
+
# Flesch-Kincaid Grade Level
|
252
|
+
(0.39 * (words.size / sentences.size.to_f)) +
|
253
|
+
(11.8 * (syllables / words.size.to_f)) - 15.59
|
254
|
+
end
|
255
|
+
|
256
|
+
# Count syllables in a word
|
257
|
+
def count_syllables(word)
|
258
|
+
word.downcase.gsub(/[^a-z]/, '').scan(/[aeiouy]+/).size
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
metadata
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: string_wizard
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Hassan Tahir
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-05-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '13.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '13.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: nokogiri
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.10'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.10'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: json
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '2.3'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '2.3'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: date
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.0'
|
97
|
+
description: Provides a wizard's toolkit of string manipulation methods that aren't
|
98
|
+
available in Ruby's standard library
|
99
|
+
email:
|
100
|
+
- hassantahirjaura@gmail.com
|
101
|
+
executables: []
|
102
|
+
extensions: []
|
103
|
+
extra_rdoc_files: []
|
104
|
+
files:
|
105
|
+
- lib/string_enhancer/core_ext.rb
|
106
|
+
- lib/string_enhancer/encryption.rb
|
107
|
+
- lib/string_enhancer/pattern_matcher.rb
|
108
|
+
- lib/string_enhancer/transformer.rb
|
109
|
+
- lib/string_enhancer/validator.rb
|
110
|
+
- lib/string_enhancer/version.rb
|
111
|
+
- lib/string_wizard.rb
|
112
|
+
- lib/string_wizard/version.rb
|
113
|
+
homepage: https://github.com/hassantahir176/string_wizard
|
114
|
+
licenses:
|
115
|
+
- MIT
|
116
|
+
metadata: {}
|
117
|
+
post_install_message:
|
118
|
+
rdoc_options: []
|
119
|
+
require_paths:
|
120
|
+
- lib
|
121
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: 2.6.0
|
126
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
requirements: []
|
132
|
+
rubygems_version: 3.5.22
|
133
|
+
signing_key:
|
134
|
+
specification_version: 4
|
135
|
+
summary: A magical collection of string manipulation and validation methods
|
136
|
+
test_files: []
|