email_inquire 0.0.0 → 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 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