email_address 0.1.2 → 0.2.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.
@@ -1,5 +1,6 @@
1
- module EmailAddress
1
+ # frozen_string_literal: true
2
2
 
3
+ module EmailAddress
3
4
  # ActiveRecord validator class for validating an email
4
5
  # address with this library.
5
6
  # Note the initialization happens once per process.
@@ -14,14 +15,13 @@ module EmailAddress
14
15
  # Default field: :email or :email_address (first found)
15
16
  #
16
17
  class ActiveRecordValidator < ActiveModel::Validator
17
-
18
- def initialize(options={})
18
+ def initialize(options = {})
19
19
  @opt = options
20
20
  end
21
21
 
22
22
  def validate(r)
23
23
  if @opt[:fields]
24
- @opt[:fields].each {|f| validate_email(r, f) }
24
+ @opt[:fields].each { |f| validate_email(r, f) }
25
25
  elsif @opt[:field]
26
26
  validate_email(r, @opt[:field])
27
27
  elsif r.respond_to? :email
@@ -31,16 +31,15 @@ module EmailAddress
31
31
  end
32
32
  end
33
33
 
34
- def validate_email(r,f)
34
+ def validate_email(r, f)
35
35
  return if r[f].nil?
36
- e = EmailAddress.new(r[f])
36
+ e = Address.new(r[f])
37
37
  unless e.valid?
38
- r.errors[f] << (@opt[:message] ||
39
- EmailAddress::Config.error_messages[:invalid_address] ||
40
- "Invalid Email Address")
38
+ error_message = @opt[:message] ||
39
+ Config.error_message(:invalid_address, I18n.locale.to_s) ||
40
+ "Invalid Email Address"
41
+ r.errors.add(f, error_message)
41
42
  end
42
43
  end
43
-
44
44
  end
45
-
46
45
  end
@@ -1,35 +1,48 @@
1
- require 'digest/sha1'
2
- require 'digest/md5'
1
+ # frozen_string_literal: true
2
+
3
+ require "digest/sha1"
4
+ require "digest/sha2"
5
+ require "digest/md5"
3
6
 
4
7
  module EmailAddress
5
8
  # Implements the Email Address container, which hold the Local
6
- # (EmailAddress::Local) and Host (Email::AddressHost) parts.
9
+ # (EmailAddress::Local) and Host (EmailAddress::Host) parts.
7
10
  class Address
8
11
  include Comparable
9
- attr_accessor :original, :local, :host, :config
12
+ include Rewriter
13
+
14
+ attr_accessor :original, :local, :host, :config, :reason, :locale
10
15
 
11
- CONVENTIONAL_REGEX = /\A#{::EmailAddress::Local::CONVENTIONAL_MAILBOX_WITHIN}
12
- @#{::EmailAddress::Host::DNS_HOST_REGEX}\z/x
13
- STANDARD_REGEX = /\A#{::EmailAddress::Local::STANDARD_LOCAL_WITHIN}
14
- @#{::EmailAddress::Host::DNS_HOST_REGEX}\z/x
15
- RELAXED_REGEX = /\A#{::EmailAddress::Local::RELAXED_MAILBOX_WITHIN}
16
- @#{::EmailAddress::Host::DNS_HOST_REGEX}\z/x
16
+ CONVENTIONAL_REGEX = /\A#{Local::CONVENTIONAL_MAILBOX_WITHIN}
17
+ @#{Host::DNS_HOST_REGEX}\z/x
18
+ STANDARD_REGEX = /\A#{Local::STANDARD_LOCAL_WITHIN}
19
+ @#{Host::DNS_HOST_REGEX}\z/x
20
+ RELAXED_REGEX = /\A#{Local::RELAXED_MAILBOX_WITHIN}
21
+ @#{Host::DNS_HOST_REGEX}\z/x
17
22
 
18
23
  # Given an email address of the form "local@hostname", this sets up the
19
24
  # instance, and initializes the address to the "normalized" format of the
20
25
  # address. The original string is available in the #original method.
21
- def initialize(email_address, config={})
22
- email_address = email_address.strip if email_address
23
- @original = email_address
24
- email_address||= ""
25
- if lh = email_address.match(/(.+)@(.+)/)
26
- (_, local, host) = lh.to_a
26
+ def initialize(email_address, config = {}, locale = "en")
27
+ @config = Config.new(config)
28
+ @original = email_address
29
+ @locale = locale
30
+ email_address = (email_address || "").strip
31
+ email_address = parse_rewritten(email_address) unless config[:skip_rewrite]
32
+ local, host = Address.split_local_host(email_address)
33
+
34
+ @host = Host.new(host, @config, locale)
35
+ @local = Local.new(local, @config, @host, locale)
36
+ @error = @error_message = nil
37
+ end
38
+
39
+ # Given an email address, this returns an array of [local, host] parts
40
+ def self.split_local_host(email)
41
+ if (lh = email.match(/(.+)@(.+)/))
42
+ lh.to_a[1, 2]
27
43
  else
28
- (local, host) = [email_address, '']
44
+ [email, ""]
29
45
  end
30
- @host = EmailAddress::Host.new(host, config)
31
- @config = @host.config
32
- @local = EmailAddress::Local.new(local, @config)
33
46
  end
34
47
 
35
48
  ############################################################################
@@ -42,26 +55,26 @@ module EmailAddress
42
55
 
43
56
  # Everything to the left of the @ in the address, called the local part.
44
57
  def left
45
- self.local.to_s
58
+ local.to_s
46
59
  end
47
60
 
48
61
  # Returns the mailbox portion of the local port, with no tags. Usually, this
49
62
  # can be considered the user account or role account names. Some systems
50
63
  # employ dynamic email addresses which don't have the same meaning.
51
64
  def mailbox
52
- self.local.mailbox
65
+ local.mailbox
53
66
  end
54
67
 
55
68
  # Returns the tag part of the local address, or nil if not given.
56
69
  def tag
57
- self.local.tag
70
+ local.tag
58
71
  end
59
72
 
60
73
  # Retuns any comments parsed from the local part of the email address.
61
74
  # This is retained for inspection after construction, even if it is
62
75
  # removed from the normalized email address.
63
76
  def comment
64
- self.local.comment
77
+ local.comment
65
78
  end
66
79
 
67
80
  ############################################################################
@@ -75,8 +88,8 @@ module EmailAddress
75
88
  def host_name
76
89
  @host.host_name
77
90
  end
78
- alias :right :host_name
79
- alias :hostname :host_name
91
+ alias_method :right, :host_name
92
+ alias_method :hostname, :host_name
80
93
 
81
94
  # Returns the ESP (Email Service Provider) or ISP name derived
82
95
  # using the provider configuration rules.
@@ -92,18 +105,18 @@ module EmailAddress
92
105
  def normal
93
106
  if !@original
94
107
  @original
95
- elsif self.local.to_s.size == 0
108
+ elsif local.to_s.size == 0
96
109
  ""
97
- elsif self.host.to_s.size == 0
98
- self.local.to_s
110
+ elsif host.to_s.size == 0
111
+ local.to_s
99
112
  else
100
- "#{self.local.to_s}@#{self.host.to_s}"
113
+ "#{local}@#{host}"
101
114
  end
102
115
  end
103
- alias :to_s :normal
116
+ alias_method :to_s, :normal
104
117
 
105
118
  def inspect
106
- "#<EmailAddress::Address:0x#{self.object_id.to_s(16)} address=\"#{self.to_s}\">"
119
+ "#<#{self.class}:0x#{object_id.to_s(16)} address=\"#{self}\">"
107
120
  end
108
121
 
109
122
  # Returns the canonical email address according to the provider
@@ -111,51 +124,60 @@ module EmailAddress
111
124
  # spaves and comments and tags, and any extraneous part of the address
112
125
  # not considered a unique account by the provider.
113
126
  def canonical
114
- c = self.local.canonical
115
- c += "@" + self.host.canonical if self.host.canonical && self.host.canonical > " "
127
+ c = local.canonical
128
+ c += "@" + host.canonical if host.canonical && host.canonical > " "
116
129
  c
117
130
  end
118
131
 
119
132
  # True if the given address is already in it's canonical form.
120
133
  def canonical?
121
- self.canonical == self.to_s
134
+ canonical == to_s
135
+ end
136
+
137
+ # The base address is the mailbox, without tags, and host.
138
+ def base
139
+ mailbox + "@" + hostname
122
140
  end
123
141
 
124
142
  # Returns the redacted form of the address
125
143
  # This format is defined by this libaray, and may change as usage increases.
126
144
  # Takes either :sha1 (default) or :md5 as the argument
127
- def redact(digest=:sha1)
128
- raise "Unknown Digest type: #{digest}" unless %i(sha1 md5).include?(digest)
129
- return self.to_s if self.local.redacted?
130
- r = %Q({#{send(digest)}})
131
- r += "@" + self.host.to_s if self.host.to_s && self.host.to_s > " "
145
+ def redact(digest = :sha1)
146
+ raise "Unknown Digest type: #{digest}" unless %i[sha1 md5].include?(digest)
147
+ return to_s if local.redacted?
148
+ r = %({#{send(digest)}})
149
+ r += "@" + host.to_s if host.to_s && host.to_s > " "
132
150
  r
133
151
  end
134
152
 
135
153
  # True if the address is already in the redacted state.
136
154
  def redacted?
137
- self.local.redacted?
155
+ local.redacted?
138
156
  end
139
157
 
140
158
  # Returns the munged form of the address, by default "mailbox@domain.tld"
141
159
  # returns "ma*****@do*****".
142
160
  def munge
143
- [self.local.munge, self.host.munge].join("@")
161
+ [local.munge, host.munge].join("@")
144
162
  end
145
163
 
146
- # Returns and MD5 of the canonical address form. Some cross-system systems
164
+ # Returns and MD5 of the base address form. Some cross-system systems
147
165
  # use the email address MD5 instead of the actual address to refer to the
148
166
  # same shared user identity without exposing the actual address when it
149
167
  # is not known in common.
150
- def reference
151
- Digest::MD5.hexdigest(self.canonical)
168
+ def reference(form = :base)
169
+ Digest::MD5.hexdigest(send(form))
152
170
  end
153
- alias :md5 :reference
171
+ alias_method :md5, :reference
154
172
 
155
- # This returns the SHA1 digest (in a hex string) of the canonical email
173
+ # This returns the SHA1 digest (in a hex string) of the base email
156
174
  # address. See #md5 for more background.
157
- def sha1
158
- Digest::SHA1.hexdigest((canonical||"") + (@config[:sha1_secret]||""))
175
+ def sha1(form = :base)
176
+ Digest::SHA1.hexdigest((send(form) || "") + (@config[:sha1_secret] || ""))
177
+ end
178
+
179
+ def sha256(form = :base)
180
+ Digest::SHA256.hexdigest((send(form) || "") + (@config[:sha256_secret] || ""))
159
181
  end
160
182
 
161
183
  #---------------------------------------------------------------------------
@@ -164,42 +186,42 @@ module EmailAddress
164
186
 
165
187
  # Equal matches the normalized version of each address. Use the Threequal to check
166
188
  # for match on canonical or redacted versions of addresses
167
- def ==(other_email)
168
- self.to_s == other_email.to_s
189
+ def ==(other)
190
+ to_s == other.to_s
169
191
  end
170
- alias :eql? :==
171
- alias :equal? :==
192
+ alias_method :eql?, :==
193
+ alias_method :equal?, :==
172
194
 
173
195
  # Return the <=> or CMP comparison operator result (-1, 0, +1) on the comparison
174
196
  # of this addres with another, using the canonical or redacted forms.
175
197
  def same_as?(other_email)
176
198
  if other_email.is_a?(String)
177
- other_email = EmailAddress::Address.new(other_email)
199
+ other_email = Address.new(other_email)
178
200
  end
179
201
 
180
- self.canonical == other_email.canonical ||
181
- self.redact == other_email.canonical ||
182
- self.canonical == other_email.redact
202
+ canonical == other_email.canonical ||
203
+ redact == other_email.canonical ||
204
+ canonical == other_email.redact
183
205
  end
184
- alias :include? :same_as?
206
+ alias_method :include?, :same_as?
185
207
 
186
208
  # Return the <=> or CMP comparison operator result (-1, 0, +1) on the comparison
187
209
  # of this addres with another, using the normalized form.
188
- def <=>(other_email)
189
- self.to_s <=> other_email.to_s
210
+ def <=>(other)
211
+ to_s <=> other.to_s
190
212
  end
191
213
 
192
214
  # Address matches one of these Matcher rule patterns
193
215
  def matches?(*rules)
194
216
  rules.flatten!
195
- match = self.local.matches?(rules)
196
- match ||= self.host.matches?(rules)
217
+ match = local.matches?(rules)
218
+ match ||= host.matches?(rules)
197
219
  return match if match
198
220
 
199
221
  # Does "root@*.com" match "root@example.com" domain name
200
222
  rules.each do |r|
201
- if r =~ /.+@.+/
202
- return r if File.fnmatch?(r, self.to_s)
223
+ if /.+@.+/.match?(r)
224
+ return r if File.fnmatch?(r, to_s)
203
225
  end
204
226
  end
205
227
  false
@@ -211,39 +233,63 @@ module EmailAddress
211
233
 
212
234
  # Returns true if this address is considered valid according to the format
213
235
  # configured for its provider, It test the normalized form.
214
- def valid?(options={})
236
+ def valid?(options = {})
215
237
  @error = nil
216
- unless self.local.valid?
217
- return set_error :invalid_mailbox
238
+ unless local.valid?
239
+ return set_error local.error
218
240
  end
219
- unless self.host.valid?
220
- return set_error :invalid_host
241
+ unless host.valid?
242
+ return set_error host.error_message
221
243
  end
222
- if @config[:address_size] && !@config[:address_size].include?(self.to_s.size)
244
+ if @config[:address_size] && !@config[:address_size].include?(to_s.size)
223
245
  return set_error :exceeds_size
224
246
  end
225
247
  if @config[:address_validation].is_a?(Proc)
226
- unless @config[:address_validation].call(self.to_s)
248
+ unless @config[:address_validation].call(to_s)
227
249
  return set_error :not_allowed
228
250
  end
229
251
  else
230
- return false unless self.local.valid?
231
- return false unless self.host.valid?
232
- end
233
- if !@config[:address_local] && !self.hostname.include?(".")
234
- return set_error :incomplete_domain
252
+ return false unless local.valid?
253
+ return false unless host.valid?
235
254
  end
236
255
  true
237
256
  end
238
257
 
239
- def set_error(err)
240
- @error = EmailAddress::Config.error_messages[err] || err
258
+ # Connects to host to test if user can receive email. This should NOT be performed
259
+ # as an email address check, but is provided to assist in problem resolution.
260
+ # If you abuse this, you *could* be blocked by the ESP.
261
+ def connect
262
+ smtp = Net::SMTP.new(host_name || ip_address)
263
+ smtp.start(@config[:smtp_helo_name] || "localhost")
264
+ smtp.mailfrom @config[:smtp_mail_from] || "postmaster@localhost"
265
+ smtp.rcptto to_s
266
+ # p [:connect]
267
+ smtp.finish
268
+ true
269
+ rescue Net::SMTPUnknownError => e
270
+ set_error(:address_unknown, e.to_s)
271
+ rescue Net::SMTPFatalError => e
272
+ set_error(:address_unknown, e.to_s)
273
+ rescue SocketError => e
274
+ set_error(:address_unknown, e.to_s)
275
+ ensure
276
+ if smtp&.started?
277
+ smtp.finish
278
+ end
279
+ !!@error
280
+ end
281
+
282
+ def set_error(err, reason = nil)
283
+ @error = err
284
+ @reason = reason
285
+ @error_message = Config.error_message(err, locale)
241
286
  false
242
287
  end
243
288
 
289
+ attr_reader :error_message
290
+
244
291
  def error
245
- self.valid? ? nil : @error
292
+ valid? ? nil : @error_message
246
293
  end
247
-
248
294
  end
249
295
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ################################################################################
2
4
  # ActiveRecord v5.0 Custom Type
3
5
  #
@@ -27,20 +29,22 @@
27
29
  # user.canonical_email #=> "patsmith@gmail.com"
28
30
  ################################################################################
29
31
 
30
- class EmailAddress::CanonicalEmailAddressType < ActiveRecord::Type::Value
32
+ module EmailAddress
33
+ class CanonicalEmailAddressType < ActiveRecord::Type::Value
31
34
 
32
- # From user input, setter
33
- def cast(value)
34
- super(EmailAddress.canonical(value))
35
- end
35
+ # From user input, setter
36
+ def cast(value)
37
+ super(Address.new(value).canonical)
38
+ end
36
39
 
37
- # From a database value
38
- def deserialize(value)
39
- value && EmailAddress.normal(value)
40
- end
40
+ # From a database value
41
+ def deserialize(value)
42
+ value && Address.new(value).normal
43
+ end
41
44
 
42
- # To a database value (string)
43
- def serialize(value)
44
- value && EmailAddress.normal(value)
45
+ # To a database value (string)
46
+ def serialize(value)
47
+ value && Address.new(value).normal
48
+ end
45
49
  end
46
50
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module EmailAddress
2
4
  # Global configurations and for default/unknown providers. Settings are:
3
5
  #
@@ -7,11 +9,19 @@ module EmailAddress
7
9
  # :a - DNS A Record lookup (as some domains don't specify an MX incorrectly)
8
10
  # :off - Do not perform DNS lookup (Test mode, network unavailable)
9
11
  #
12
+ # * dns_timeout: nil
13
+ # False, or a timeout in seconds. Timeout on the DNS lookup, after which it will fail.
14
+ #
10
15
  # * sha1_secret ""
11
16
  # This application-level secret is appended to the email_address to compute
12
17
  # the SHA1 Digest, making it unique to your application so it can't easily be
13
18
  # discovered by comparing against a known list of email/sha1 pairs.
14
19
  #
20
+ # * sha256_secret ""
21
+ # This application-level secret is appended to the email_address to compute
22
+ # the SHA256 Digest, making it unique to your application so it can't easily be
23
+ # discovered by comparing against a known list of email/sha256 pairs.
24
+ #
15
25
  # For local part configuration:
16
26
  # * local_downcase: true
17
27
  # Downcase the local part. You probably want this for uniqueness.
@@ -68,6 +78,9 @@ module EmailAddress
68
78
  # * host_allow_ip: false,
69
79
  # Allow IP address format in host: [127.0.0.1], [IPv6:::1]
70
80
  #
81
+ # * host_local: false,
82
+ # Allow localhost, no domain, or local subdomains.
83
+ #
71
84
  # * address_validation: :parts, :smtp, ->(address) { true }
72
85
  # Address validation policy
73
86
  # :parts Validate local and host.
@@ -77,8 +90,11 @@ module EmailAddress
77
90
  # * address_size: 3..254,
78
91
  # A range specifying the size limit of the complete address
79
92
  #
80
- # * address_local: false,
81
- # Allow localhost, no domain, or local subdomains.
93
+ # * address_fqdn_domain: nil || "domain.tld"
94
+ # Configure to complete the FQDN (Fully Qualified Domain Name)
95
+ # When host is blank, this value is used
96
+ # When host is computer name only, a dot and this is appended to get the FQDN
97
+ # You probably don't want this unless you have host-local email accounts
82
98
  #
83
99
  # For provider rules to match to domain names and Exchanger hosts
84
100
  # The value is an array of match tokens.
@@ -86,64 +102,68 @@ module EmailAddress
86
102
  # * exchanger_match: %w(google.com 127.0.0.1 10.9.8.0/24 ::1/64)
87
103
  #
88
104
 
105
+ require "yaml"
106
+
89
107
  class Config
90
108
  @config = {
91
- dns_lookup: :mx, # :mx, :a, :off
92
- sha1_secret: "",
93
- munge_string: "*****",
94
-
95
- local_downcase: true,
96
- local_fix: true,
97
- local_encoding: :ascii, # :ascii, :unicode,
98
- local_parse: nil, # nil, Proc
99
- local_format: :conventional, # :conventional, :relaxed, :redacted, :standard, Proc
100
- local_size: 1..64,
101
- tag_separator: '+', # nil, character
102
- mailbox_size: 1..64, # without tag
103
- mailbox_canonical: nil, # nil, Proc
104
- mailbox_validator: nil, # nil, Proc
105
-
106
- host_encoding: :punycode || :unicode,
107
- host_validation: :mx || :a || :connect,
108
- host_size: 1..253,
109
- host_allow_ip: false,
109
+ dns_lookup: :mx, # :mx, :a, :off
110
+ dns_timeout: nil,
111
+ sha1_secret: "",
112
+ sha256_secret: "",
113
+ munge_string: "*****",
114
+
115
+ local_downcase: true,
116
+ local_fix: false,
117
+ local_encoding: :ascii, # :ascii, :unicode,
118
+ local_parse: nil, # nil, Proc
119
+ local_format: :conventional, # :conventional, :relaxed, :redacted, :standard, Proc
120
+ local_size: 1..64,
121
+ tag_separator: "+", # nil, character
122
+ mailbox_size: 1..64, # without tag
123
+ mailbox_canonical: nil, # nil, Proc
124
+ mailbox_validator: nil, # nil, Proc
125
+
126
+ host_encoding: :punycode || :unicode,
127
+ host_validation: :mx || :a || :connect || :syntax,
128
+ host_size: 1..253,
129
+ host_allow_ip: false,
130
+ host_remove_spaces: false,
131
+ host_local: false,
110
132
 
111
133
  address_validation: :parts, # :parts, :smtp, Proc
112
- address_size: 3..254,
113
- address_localhost: false,
134
+ address_size: 3..254,
135
+ address_fqdn_domain: nil # Fully Qualified Domain Name = [host].[domain.tld]
114
136
  }
115
137
 
138
+ # 2018-04: AOL and Yahoo now under "oath.com", owned by Verizon. Keeping separate for now
116
139
  @providers = {
117
140
  aol: {
118
- host_match: %w(aol. compuserve. netscape. aim. cs.),
141
+ host_match: %w[aol. compuserve. netscape. aim. cs.]
119
142
  },
120
143
  google: {
121
- host_match: %w(gmail.com googlemail.com),
122
- exchanger_match: %w(google.com),
123
- local_size: 5..64,
124
- mailbox_canonical: ->(m) {m.gsub('.','')},
144
+ host_match: %w[gmail.com googlemail.com],
145
+ exchanger_match: %w[google.com googlemail.com],
146
+ local_size: 5..64,
147
+ local_private_size: 1..64, # When hostname not in host_match (private label)
148
+ mailbox_canonical: ->(m) { m.delete(".") }
125
149
  },
126
150
  msn: {
127
- host_match: %w(msn. hotmail. outlook. live.),
128
- mailbox_validator: ->(m,t) { m =~ /\A[a-z0-9][\.\-a-z0-9]{5,29}\z/i},
151
+ host_match: %w[msn. hotmail. outlook. live.],
152
+ exchanger_match: %w[outlook.com],
153
+ mailbox_validator: ->(m, t) { m =~ /\A\w[\-\w]*(?:\.[\-\w]+)*\z/i }
129
154
  },
130
155
  yahoo: {
131
- host_match: %w(yahoo. ymail. rocketmail.),
132
- exchanger_match: %w(yahoodns yahoo-inc),
133
- },
156
+ host_match: %w[yahoo. ymail. rocketmail.],
157
+ exchanger_match: %w[yahoodns yahoo-inc]
158
+ }
134
159
  }
135
160
 
136
- @errors = {
137
- invalid_address: "Invalid Email Address",
138
- invalid_mailbox: "Invalid Recipient/Mailbox",
139
- invalid_host: "Invalid Host/Domain Name",
140
- exceeds_size: "Address too long",
141
- not_allowed: "Address is not allowed",
142
- incomplete_domain: "Domain name is incomplete",
143
- }
161
+ # Loads messages: {"en"=>{"email_address"=>{"invalid_address"=>"Invalid Email Address",...}}}
162
+ # Rails/I18n gem: t(email_address.error, scope: "email_address")
163
+ @errors = YAML.load_file(File.dirname(__FILE__) + "/messages.yaml")
144
164
 
145
165
  # Set multiple default configuration settings
146
- def self.configure(config={})
166
+ def self.configure(config = {})
147
167
  @config.merge!(config)
148
168
  end
149
169
 
@@ -154,30 +174,61 @@ module EmailAddress
154
174
  end
155
175
 
156
176
  # Returns the hash of Provider rules
157
- def self.providers
158
- @providers
177
+ class << self
178
+ attr_reader :providers
159
179
  end
160
180
 
161
181
  # Configure or lookup a provider by name.
162
- def self.provider(name, config={})
182
+ def self.provider(name, config = {})
163
183
  name = name.to_sym
164
184
  if config.size > 0
165
- @providers[name] ||= @config.clone
166
- @providers[name].merge!(config)
185
+ @providers[name.to_sym] = config
167
186
  end
168
187
  @providers[name]
169
188
  end
170
189
 
190
+ def self.error_message(name, locale = "en")
191
+ @errors[locale]["email_address"][name.to_s] || name.to_s
192
+ end
193
+
171
194
  # Customize your own error message text.
172
- def self.error_messages(hash=nil)
173
- @errors = @errors.merge(hash) if hash
174
- @errors
195
+ def self.error_messages(hash = {}, locale = "en", *extra)
196
+ hash = extra.first if extra.first.is_a? Hash
197
+
198
+ @errors[locale] ||= {}
199
+ @errors[locale]["email_address"] ||= {}
200
+
201
+ unless hash.nil? || hash.empty?
202
+ @errors[locale]["email_address"] = @errors[locale]["email_address"].merge(hash)
203
+ end
204
+
205
+ @errors[locale]["email_address"]
175
206
  end
176
207
 
177
208
  def self.all_settings(*configs)
178
209
  config = @config.clone
179
- configs.each {|c| config.merge!(c) }
210
+ configs.each { |c| config.merge!(c) }
180
211
  config
181
212
  end
213
+
214
+ def initialize(overrides = {})
215
+ @config = Config.all_settings(overrides)
216
+ end
217
+
218
+ def []=(setting, value)
219
+ @config[setting.to_sym] = value
220
+ end
221
+
222
+ def [](setting)
223
+ @config[setting.to_sym]
224
+ end
225
+
226
+ def configure(settings)
227
+ @config = @config.merge(settings)
228
+ end
229
+
230
+ def to_h
231
+ @config
232
+ end
182
233
  end
183
234
  end