email_inquire 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1d4d6c692446838e7123938c97e1fe109517a59c
4
- data.tar.gz: 15e35d56d46e72e3eed50d19f61c2c0cce594da8
3
+ metadata.gz: 8791dcafc0744b953fdccdbe7ff3f5497c85d91d
4
+ data.tar.gz: 4770b7ddbdbfddcb0851410cda5d87ad4a50f927
5
5
  SHA512:
6
- metadata.gz: 929052741ff20a1d7b991aa2ebcc52ff5a61e62de2bf4a7f4049af67c14065ad68ee651ad2c25c0af5300997d24d813cf9f4fe1a55840b79190d54461e85bc7e
7
- data.tar.gz: f49c66b8bdb2cb6eed4be7227d140016d543bec1e0797e364b3ec6b2af482554fb2d9c231fe4860791028f91e2a470a9c566615d1a15034d8e71e682ad3c1ac1
6
+ metadata.gz: d5372d89873571ac06412c1128cf060740c02cb17cd502ff46d5ae0373c115b2f7c097dfcfa7e870bcedbe8a6568cae067a9f30d9402cb3df7b95c548eeb0ac0
7
+ data.tar.gz: 17d939729cbeea8a348127da0aaa19517012961801d341171e42e8c398d0d9277edd185331a1445b85e93ba97125b4bd79e01e0ea3eedb1f6444f6e5f7197af3
data/.rubocop.yml ADDED
@@ -0,0 +1,83 @@
1
+ AllCops:
2
+ Exclude:
3
+ - 'bin/*'
4
+ TargetRubyVersion: 2.3
5
+
6
+ Metrics/AbcSize:
7
+ Max: 20 # default: 15
8
+
9
+ Metrics/CyclomaticComplexity:
10
+ Enabled: false # todo
11
+ Max: 10 # default: 6
12
+
13
+ Metrics/LineLength:
14
+ Enabled: false # todo
15
+ Max: 100 # default: 80
16
+
17
+ Metrics/MethodLength:
18
+ Exclude:
19
+ - "db/migrate/*"
20
+ Max: 12 # default: 10
21
+
22
+ Metrics/PerceivedComplexity:
23
+ Enabled: false # todo
24
+ Max: 10 # default: 7
25
+
26
+ Style/AccessModifierIndentation:
27
+ EnforcedStyle: indent
28
+ SupportedStyles:
29
+ - outdent
30
+ - indent
31
+
32
+ Style/AlignHash:
33
+ EnforcedHashRocketStyle: key
34
+ EnforcedColonStyle: key
35
+ EnforcedLastArgumentHashStyle: always_inspect
36
+
37
+ Style/AlignParameters:
38
+ EnforcedStyle: with_fixed_indentation
39
+
40
+ Style/BracesAroundHashParameters:
41
+ Enabled: false
42
+
43
+ Style/Documentation:
44
+ Enabled: false
45
+
46
+ Style/EmptyLinesAroundClassBody:
47
+ EnforcedStyle: empty_lines
48
+
49
+ Style/EmptyLinesAroundModuleBody:
50
+ EnforcedStyle: empty_lines
51
+
52
+ Style/FirstParameterIndentation:
53
+ EnforcedStyle: consistent
54
+
55
+ Style/FrozenStringLiteralComment:
56
+ Enabled: true
57
+
58
+ Style/IndentHash:
59
+ EnforcedStyle: consistent
60
+
61
+ Style/ModuleFunction:
62
+ Enabled: false
63
+
64
+ Style/MultilineMethodCallIndentation:
65
+ EnforcedStyle: indented
66
+
67
+ Style/MultilineOperationIndentation:
68
+ EnforcedStyle: indented
69
+
70
+ Style/SignalException:
71
+ EnforcedStyle: only_raise
72
+
73
+ Style/StringLiterals:
74
+ EnforcedStyle: double_quotes
75
+
76
+ Style/StringLiteralsInInterpolation:
77
+ EnforcedStyle: double_quotes
78
+
79
+ Style/TrailingCommaInArguments:
80
+ EnforcedStyleForMultiline: no_comma
81
+
82
+ Style/TrailingCommaInLiteral:
83
+ EnforcedStyleForMultiline: comma
data/.travis.yml CHANGED
@@ -1,5 +1,9 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
- - 2.3.1
5
- before_install: gem install bundler -v 1.13.1
4
+ - 2.1.8
5
+ - 2.2.5
6
+ - 2.3.3
7
+ - 2.4.0
8
+ before_install: gem install bundler -v 1.13.7
9
+ bundler_args: --without local
data/Gemfile CHANGED
@@ -1,4 +1,11 @@
1
- source 'https://rubygems.org'
1
+ # frozen_string_literal: true
2
+ source "https://rubygems.org"
2
3
 
3
4
  # Specify your gem's dependencies in email_inquire.gemspec
4
5
  gemspec
6
+
7
+ group :local do
8
+ # Guard
9
+ gem "guard-rspec", require: false
10
+ gem "terminal-notifier-guard", require: false # OS X
11
+ end
data/Guardfile ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ guard :rspec, cmd: "bundle exec rspec" do
3
+ require "guard/rspec/dsl"
4
+ dsl = Guard::RSpec::Dsl.new(self)
5
+
6
+ # RSpec files
7
+ rspec = dsl.rspec
8
+ watch(rspec.spec_helper) { rspec.spec_dir }
9
+ watch(rspec.spec_support) { rspec.spec_dir }
10
+ watch(rspec.spec_files)
11
+
12
+ # Ruby files
13
+ ruby = dsl.ruby
14
+ dsl.watch_spec_files_for(ruby.lib_files)
15
+ end
data/README.md CHANGED
@@ -1,7 +1,53 @@
1
1
  # EmailInquire
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/email_inquire.svg)](https://badge.fury.io/rb/email_inquire) [![Build Status](https://travis-ci.org/maximeg/email_inquire.svg?branch=master)](https://travis-ci.org/maximeg/email_inquire)
4
+
3
5
  EmailInquire is a library to validate email for common typos and one-time email provider.
4
6
 
7
+ ## Why?
8
+
9
+ Before an user is an user, it's a visitor. And he must register to be so. What if he makes a typo while
10
+ entering its email address during the registration ?
11
+ If he didn't notice, you just lost him. He won't be able to sign in next time.
12
+
13
+ Your users :
14
+
15
+ - may not be as tech saavy as you;
16
+ - may not remember exactly their email address;
17
+ - may make a typo while typing their email address (very very common on a mobile keyboard).
18
+
19
+ While we can't do so much for the name part of the email address, for the domain part, we can be smart!
20
+
21
+ ### Supported cases
22
+
23
+ One char typo for common email providers of France, United Kingdom and USA:
24
+
25
+ - `gmil.com` => hint `gmail.com`
26
+ - `hitmail.com` => hint `hotmail.com`
27
+ - `outloo.com` => hint `outlook.com`
28
+ - `virinmedia.com` => hint `virginmedia.com`
29
+
30
+ United Kingdom `.xx.uk`:
31
+
32
+ - `foo.couk` => hint `foo.co.uk`
33
+ - `fooco.uk` => hint `foo.co.uk`
34
+ - `foo.uk` => hint `foo.co.uk`
35
+ - `foo.judiciary.uk` => ok!
36
+
37
+ Provider with an unique TLD domain:
38
+
39
+ - `gmail.fr` => hint `gmail.com`
40
+ - `gmail.de` => hint `gmail.com`
41
+ - `google.com` => hint `gmail.com`
42
+ - `free.com` => hint `free.fr`
43
+ - `laposte.com` => hint `laposte.net`
44
+ - `laposte.fr` => hint `laposte.net`
45
+
46
+ One time email providers:
47
+
48
+ - `yopmail.com` => invalid
49
+ - more to come.
50
+
5
51
  ## Installation
6
52
 
7
53
  Add this line to your application's Gemfile:
@@ -20,7 +66,70 @@ Or install it yourself as:
20
66
 
21
67
  ## Usage
22
68
 
23
- TODO: Write usage instructions here
69
+ Use `EmailInquire.validate(email)`, you'll get a `EmailInquire::Response` that represents weither
70
+ or not the email address is valid or may contain a mistake.
71
+
72
+ Methods of `EmailInquire::Response`:
73
+
74
+ | Method | Description | Possible values |
75
+ | --- | --- | --- |
76
+ | `#email` | The validated email address | `"john.doe@gnail.com"` |
77
+ | `#status` | The status of the validation | `:valid` `:invalid` or `:hint` |
78
+ | `#valid?` | Is the email valid ? | `true` or `false` |
79
+ | `#invalid?` | Is the email invalid ? | `true` or `false` |
80
+ | `#hint?` | Is there a possible mistake and you have to show an hint to the user ? | `true` or `false` |
81
+ | `#replacement` | A proposal replacement email address for when status is `:hint` | `"john.doe@gmail.com"` or nil |
82
+
83
+ ### Examples
84
+
85
+ A valid case:
86
+
87
+ ```ruby
88
+ response = EmailInquire.validate("john.doe@gmail.com")
89
+ response.status # :valid
90
+ response.valid? # true
91
+ ```
92
+
93
+ An invalid case:
94
+
95
+ ```ruby
96
+ response = EmailInquire.validate("john.doe@yopmail.com")
97
+ response.status # :invalid
98
+ response.valid? # false
99
+ response.invalid? # true
100
+ ```
101
+
102
+ A hint case:
103
+
104
+ ```ruby
105
+ response = EmailInquire.validate("john.doe@gmail.co")
106
+ response.status # :hint
107
+ response.valid? # false
108
+ response.hint? # true
109
+ response.replacement # "john.doe@gmail.com"
110
+ ```
111
+
112
+ ### Hint
113
+
114
+ I think it's important to just offer a hint to the user and to not automatically replace the
115
+ **maybe** faulty email address in the form.
116
+
117
+ A _"Did you mean xxx@yyy.zzz ?"_ has the following advantages:
118
+
119
+ - user remains in charge: we could have hinted against a perfectly valid email;
120
+ - user is educated;
121
+ - mini whaoo effect;
122
+
123
+ This _"Did you mean xxx@yyy.zzz ?"_ is better being actionable, and appearing to be so: a click or tap on it should replace the email by the suggestion.
124
+
125
+ ```
126
+ +---------------------------------------+ +---------+
127
+ | john.doe@yaho.com | | Sign Up |
128
+ +---------------------------------------+ +---------+
129
+ Did you mean john.doe@yahoo.com ?
130
+ ```
131
+
132
+ Note that you could even have this validation for your Sign In forms...
24
133
 
25
134
  ## Development
26
135
 
data/Rakefile CHANGED
@@ -1,6 +1,7 @@
1
+ # frozen_string_literal: true
1
2
  require "bundler/gem_tasks"
2
3
  require "rspec/core/rake_task"
3
4
 
4
5
  RSpec::Core::RakeTask.new(:spec)
5
6
 
6
- task :default => :spec
7
+ task default: :spec
@@ -1,7 +1,8 @@
1
1
  # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
2
+ # frozen_string_literal: true
3
+ lib = File.expand_path("../lib", __FILE__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'email_inquire/version'
5
+ require "email_inquire/version"
5
6
 
6
7
  Gem::Specification.new do |spec|
7
8
  spec.name = "email_inquire"
@@ -16,14 +17,17 @@ Gem::Specification.new do |spec|
16
17
 
17
18
  spec.required_ruby_version = ">= 2.0.0"
18
19
 
19
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
20
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
20
21
  f.match(%r{^(test|spec|features)/})
21
22
  end
22
23
  spec.bindir = "exe"
23
24
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
25
  spec.require_paths = ["lib"]
25
26
 
27
+ spec.add_runtime_dependency "damerau-levenshtein", "~> 1.2"
28
+
26
29
  spec.add_development_dependency "bundler", "~> 1.13"
27
30
  spec.add_development_dependency "rake", "~> 10.0"
28
31
  spec.add_development_dependency "rspec", "~> 3.0"
32
+ spec.add_development_dependency "rubocop", "~> 0.47"
29
33
  end
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+ require "damerau-levenshtein"
3
+
4
+ module EmailInquire
5
+
6
+ class Inquirer
7
+
8
+ def initialize(email)
9
+ @email = email.downcase
10
+ response.email = email
11
+
12
+ parse_email
13
+ end
14
+
15
+ attr_reader :domain, :email, :name
16
+
17
+ def validate
18
+ validate_typos
19
+
20
+ response
21
+ end
22
+
23
+ private
24
+
25
+ def parse_email
26
+ @name, @domain = email.split("@")
27
+ end
28
+
29
+ def response
30
+ @response ||= Response.new
31
+ end
32
+
33
+ def validate_typos
34
+ [
35
+ :validate_common_domains,
36
+ :validate_one_time_providers,
37
+ :validate_common_domain_mistakes,
38
+ :validate_uk_tld,
39
+ :validate_common_tld_mistakes,
40
+ :validate_domains_with_unique_tld,
41
+ ].each do |validator|
42
+ send(validator)
43
+ break if response.valid? || response.invalid?
44
+ end
45
+
46
+ # default
47
+ response.valid! unless response.status?
48
+ end
49
+
50
+ COMMON_DOMAIN_MISTAKES = {
51
+ /google(?!mail)/ => "gmail.com",
52
+ /windows.*\.com/ => "live.com",
53
+ }.freeze
54
+
55
+ def validate_common_domain_mistakes
56
+ COMMON_DOMAIN_MISTAKES.each do |mistake, reference|
57
+ break if domain == reference # valid!
58
+
59
+ if mistake =~ domain
60
+ response.hint!(domain: reference)
61
+ break
62
+ end
63
+ end
64
+ end
65
+
66
+ COMMON_DOMAINS = %w(
67
+ aim.com
68
+ aliceadsl.fr
69
+ aol.co.uk
70
+ aol.com
71
+ att.net
72
+ bbox.fr
73
+ bellsouth.net
74
+ blueyonder.co.uk
75
+ btinternet.com
76
+ charter.net
77
+ cox.net
78
+ free.fr
79
+ gmail.com
80
+ gmx.fr
81
+ googlemail.com
82
+ hotmail.co.uk
83
+ hotmail.com
84
+ hotmail.fr
85
+ icloud.com
86
+ laposte.net
87
+ live.co.uk
88
+ live.com
89
+ live.fr
90
+ me.com
91
+ msn.com
92
+ neuf.fr
93
+ ntlworld.com
94
+ numericable.fr
95
+ orange.fr
96
+ outlook.com
97
+ outlook.fr
98
+ rocketmail.com
99
+ sbcglobal.net
100
+ sfr.fr
101
+ sky.com
102
+ talktalk.net
103
+ verizon.net
104
+ virginmedia.com
105
+ wanadoo.fr
106
+ yahoo.co.uk
107
+ yahoo.com
108
+ yahoo.fr
109
+ ymail.com
110
+ ).freeze
111
+
112
+ def validate_common_domains
113
+ return response.valid! if COMMON_DOMAINS.include?(domain)
114
+
115
+ COMMON_DOMAINS.each do |reference|
116
+ distance = ::DamerauLevenshtein.distance(domain, reference, 2, 3)
117
+ if distance <= 1
118
+ response.hint!(domain: reference)
119
+ break
120
+ end
121
+ end
122
+ end
123
+
124
+ COMMON_TLD_MISTAKES = {
125
+ ".couk" => ".co.uk",
126
+ ".com.com" => ".com",
127
+ }.freeze
128
+
129
+ def validate_common_tld_mistakes
130
+ COMMON_TLD_MISTAKES.each do |mistake, reference|
131
+ break if !mistake.end_with?(reference) && domain.end_with?(reference)
132
+
133
+ if domain.end_with?(mistake)
134
+ response.hint!(domain: domain.gsub(/#{mistake}\z/, reference))
135
+ break
136
+ end
137
+ end
138
+ end
139
+
140
+ VALID_UK_TLD = %w(
141
+ .ac.uk
142
+ .co.uk
143
+ .gov.uk
144
+ .judiciary.uk
145
+ .ltd.uk
146
+ .me.uk
147
+ .mod.uk
148
+ .net.uk
149
+ .nhs.uk
150
+ .nic.uk
151
+ .org.uk
152
+ .parliament.uk
153
+ .plc.uk
154
+ .police.uk
155
+ .sch.uk
156
+ ).freeze
157
+
158
+ def validate_uk_tld
159
+ return unless domain.end_with?(".uk")
160
+
161
+ return if VALID_UK_TLD.any? do |reference|
162
+ domain.end_with?(reference)
163
+ end
164
+
165
+ new_domain = domain.dup
166
+ new_domain.gsub!(/(?<!\.)co\.uk\z/, ".co.uk")
167
+ new_domain.gsub!(/\.c[^o]\.uk\z/, ".co.uk")
168
+ new_domain.gsub!(/\.[^c]o\.uk\z/, ".co.uk")
169
+ new_domain.gsub!(/(?<!co)\.uk\z/, ".co.uk")
170
+
171
+ response.hint!(domain: new_domain) if new_domain != domain
172
+ end
173
+
174
+ UNIQUE_TLD_DOMAINS = %w(
175
+ free.fr
176
+ gmail.com
177
+ laposte.net
178
+ sfr.fr
179
+ wanadoo.fr
180
+ ).freeze
181
+
182
+ def validate_domains_with_unique_tld
183
+ base, tld = domain.split(".")
184
+
185
+ UNIQUE_TLD_DOMAINS.each do |reference|
186
+ reference_base, reference_tld = reference.split(".")
187
+
188
+ if base == reference_base && tld != reference_tld
189
+ response.hint!(domain: reference)
190
+ break
191
+ end
192
+ end
193
+ end
194
+
195
+ # https://github.com/wesbos/burner-email-providers
196
+ ONE_TIME_EMAIL_PROVIDERS = %w(
197
+ yopmail.com
198
+ ).freeze
199
+
200
+ def validate_one_time_providers
201
+ response.invalid! if ONE_TIME_EMAIL_PROVIDERS.include?(domain)
202
+ end
203
+
204
+ end
205
+
206
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+ module EmailInquire
3
+
4
+ class Response
5
+
6
+ attr_accessor :email, :replacement, :status
7
+
8
+ def hint!(domain: nil)
9
+ self.status = :hint
10
+
11
+ old_name, _old_domain = email.split("@")
12
+ self.replacement = "#{old_name}@#{domain}" if domain
13
+ end
14
+
15
+ def hint?
16
+ status == :hint
17
+ end
18
+
19
+ def invalid!
20
+ self.status = :invalid
21
+ end
22
+
23
+ def invalid?
24
+ status == :invalid
25
+ end
26
+
27
+ def status?
28
+ !status.nil?
29
+ end
30
+
31
+ def valid!
32
+ self.status = :valid
33
+ end
34
+
35
+ def valid?
36
+ status == :valid
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -1,5 +1,6 @@
1
+ # frozen_string_literal: true
1
2
  module EmailInquire
2
3
 
3
- VERSION = "0.0.0"
4
+ VERSION = "0.1.0"
4
5
 
5
6
  end
data/lib/email_inquire.rb CHANGED
@@ -1,5 +1,13 @@
1
+ # frozen_string_literal: true
1
2
  require "email_inquire/version"
3
+ require "email_inquire/inquirer"
4
+ require "email_inquire/response"
2
5
 
3
6
  module EmailInquire
4
- # Your code goes here...
7
+
8
+ def self.validate(email)
9
+ inquirer = Inquirer.new(email)
10
+ inquirer.validate
11
+ end
12
+
5
13
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: email_inquire
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maxime Garcia
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-10-03 00:00:00.000000000 Z
11
+ date: 2017-03-19 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: damerau-levenshtein
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.2'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: bundler
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -52,6 +66,20 @@ dependencies:
52
66
  - - "~>"
53
67
  - !ruby/object:Gem::Version
54
68
  version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.47'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.47'
55
83
  description: Library to validate email for common typos and one-time email provider
56
84
  email:
57
85
  - maxime.garcia@gmail.com
@@ -61,8 +89,10 @@ extra_rdoc_files: []
61
89
  files:
62
90
  - ".gitignore"
63
91
  - ".rspec"
92
+ - ".rubocop.yml"
64
93
  - ".travis.yml"
65
94
  - Gemfile
95
+ - Guardfile
66
96
  - LICENSE.txt
67
97
  - README.md
68
98
  - Rakefile
@@ -70,6 +100,8 @@ files:
70
100
  - bin/setup
71
101
  - email_inquire.gemspec
72
102
  - lib/email_inquire.rb
103
+ - lib/email_inquire/inquirer.rb
104
+ - lib/email_inquire/response.rb
73
105
  - lib/email_inquire/version.rb
74
106
  homepage: https://github.com/maximeg/email_inquire
75
107
  licenses:
@@ -91,7 +123,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
91
123
  version: '0'
92
124
  requirements: []
93
125
  rubyforge_project:
94
- rubygems_version: 2.6.4
126
+ rubygems_version: 2.6.10
95
127
  signing_key:
96
128
  specification_version: 4
97
129
  summary: Library to validate email for common typos and one-time email provider