diff --git a/history.textile b/history.textile index 76c72347..94b33fdb 100644 --- a/history.textile +++ b/history.textile @@ -1,5 +1,9 @@ h2. Upcoming Version. +h2. Version 1.6.6 + +* hanke: Experimental @plausible?@ feature. Checks if the given number is a plausible number. Returns @false@ if 100% not plausible, @true@ if probably true. + h2. Version 1.6.5 * hanke: Ghana (thanks jschwertfeger!). diff --git a/lib/phony.rb b/lib/phony.rb index 4e102673..d86ea9b3 100644 --- a/lib/phony.rb +++ b/lib/phony.rb @@ -12,6 +12,8 @@ require File.expand_path '../phony/national_code', __FILE__ require File.expand_path '../phony/country', __FILE__ require File.expand_path '../phony/country_codes', __FILE__ +require File.expand_path '../phony/validator', __FILE__ +require File.expand_path '../phony/validators', __FILE__ require File.expand_path '../phony/dsl', __FILE__ # Countries. @@ -37,7 +39,8 @@ module Phony # Phony uses a single country codes instance. # - @codes = CountryCodes.instance + @codes = CountryCodes.instance + @validator = Validators.instance class << self @@ -74,6 +77,16 @@ def format! phone_number, options = {} end alias formatted format alias formatted! format! + + # Makes a plausibility check. + # + # If it returns false, it is not plausible. + # If it returns true, it is unclear whether it is plausible, + # leaning towards being plausible. + # + def plausible? number, hints = {} + @validator.plausible? number, hints + end # def service? number # @codes.service? number.dup diff --git a/lib/phony/countries.rb b/lib/phony/countries.rb index c02f2c23..07b2c3a6 100644 --- a/lib/phony/countries.rb +++ b/lib/phony/countries.rb @@ -27,7 +27,9 @@ # USA, Canada, etc. # - country '1', fixed(3) >> split(3,4) + country '1', + fixed(3) >> split(3,4), + invalid_ndcs(/911/) # Kazakhstan (Republic of) & Russian Federation. # diff --git a/lib/phony/country_codes.rb b/lib/phony/country_codes.rb index 946845e4..d5c2fd43 100644 --- a/lib/phony/country_codes.rb +++ b/lib/phony/country_codes.rb @@ -6,7 +6,7 @@ module Phony # class CountryCodes - attr_reader :mapping + attr_reader :splitter_mapping attr_accessor :international_absolute_format, :international_relative_format, :national_format def initialize @@ -22,11 +22,14 @@ def self.instance @instance ||= new end - @@normalizing_pattern = /^0+|\D/ - def normalize number + @@basic_normalizing_pattern = /^0+|\D/ + def clean number # Remove non-digit chars. # - number.gsub! @@normalizing_pattern, EMPTY_STRING + number.gsub! @@basic_normalizing_pattern, EMPTY_STRING + end + def normalize number + clean number national_handler, cc, rest = split_cc number @normalize_format % [cc, national_handler.normalize(rest)] end @@ -102,7 +105,7 @@ def split_cc rest presumed_cc = '' 1.upto(3) do |i| presumed_cc << rest.slice!(0..0) - national_code_handler = mapping[i][presumed_cc] + national_code_handler = splitter_mapping[i][presumed_cc] return [national_code_handler, presumed_cc, rest] if national_code_handler end # This line is never reached as CCs are in prefix code. @@ -121,9 +124,9 @@ def add country_code, country country_code = country_code.to_s optimized_country_code_access = country_code.size - @mapping ||= {} - @mapping[optimized_country_code_access] ||= {} - @mapping[optimized_country_code_access][country_code] = country + @splitter_mapping ||= {} + @splitter_mapping[optimized_country_code_access] ||= {} + @splitter_mapping[optimized_country_code_access][country_code] = country end end diff --git a/lib/phony/dsl.rb b/lib/phony/dsl.rb index 415a95cf..6d1e49a7 100644 --- a/lib/phony/dsl.rb +++ b/lib/phony/dsl.rb @@ -46,8 +46,9 @@ class DSL # # - def country country_code, country + def country country_code, country, validator = nil Phony::CountryCodes.instance.add country_code, country + Phony::Validators.instance.add country_code, validator if validator end # National matcher & splitters. @@ -89,6 +90,12 @@ def split *local def matched_split options = {} Phony::LocalSplitters::Regex.instance_for options end + + # Validators + # + def invalid_ndcs ndc + Validator.new.ndc_check ndc + end end diff --git a/lib/phony/validator.rb b/lib/phony/validator.rb index 0e7c3c9c..81667741 100644 --- a/lib/phony/validator.rb +++ b/lib/phony/validator.rb @@ -1,62 +1,24 @@ -# # Number: A possible phone number, E164 or not. -# # Hints: Information that helps or constricts the plausibility check. -# # -# plausible? number, hints = {} -# - -# plausible? number # Uses the definitions from the country definition to plausibility check. -# plausible? number, cc: 1 # => Checks cc. -# plausible? number, pattern: /[^5]/ # Uses def, checks against split. -# plausible? number, country: 1, pattern: [3, 4, 3] # Uses given country – adds cc. -# - -# Basic plausibility is: -# * Max digits are 15. -# * Min digits are 2 (?) -# - module Phony class Validator - - # TODO Beautify. - # - def plausible? number, hints = {} - normalized = normalize number - - # False if it fails the basic check. - # - return false unless normalized.size === (2..15) - - # Hint based checking. - # - cc, ndc, *rest = split normalized - - # CC. - # - cc_needed = hints[:cc] - return false if cc_needed && cc_needed != cc - - # NDC. - # - ndc_needed = hints[:ndc] - return false if ndc_needed && ndc_needed != ndc - - # Country specific checks? - # - - # Get the country. - # - - # Check. - # + + attr_reader :ndc_checks + + def initialize + @ndc_checks = [] + end + + def plausible? ndc, rest + ndc_checks && ndc_checks.each do |ndc_check| + return false if ndc_check === ndc + end - rescue StandardError - return false + true end - - def normalize number - number + + def ndc_check ndc + @ndc_checks << ndc + self end end diff --git a/lib/phony/validators.rb b/lib/phony/validators.rb new file mode 100644 index 00000000..19b01923 --- /dev/null +++ b/lib/phony/validators.rb @@ -0,0 +1,77 @@ +# # Number: A possible phone number, E164 or not. +# # Hints: Information that helps or constricts the plausibility check. +# # +# plausible? number, hints = {} +# + +# plausible? number # Uses the definitions from the country definition to plausibility check. +# plausible? number, cc: 1 # => Checks cc. +# plausible? number, pattern: /[^5]/ # Uses def, checks against split. +# plausible? number, country: 1, pattern: [3, 4, 3] # Uses given country – adds cc. +# + +# Basic plausibility is: +# * Max digits are 15. +# * Min digits are 2 (?) +# + +module Phony + + class Validators + + def initialize + @validators = {} + end + + def self.instance + @instance ||= new + end + + # Add a specific country validator. + # + def add cc, validator + @validators[cc] = validator + end + + # Is the given number plausible? + # + def plausible? number, hints = {} + normalized = CountryCodes.instance.clean number + + # False if it fails the basic check. + # + return false unless (2..15) === normalized.size + + # Hint based checking. + # + cc, ndc, *rest = Phony.split normalized + + # CC. + # + cc_needed = hints[:cc] + return false if cc_needed && cc_needed != cc + + # NDC. + # + ndc_needed = hints[:ndc] + return false if ndc_needed && ndc_needed != ndc + + # Country specific checks. + # + validator = validator_for cc + validator.plausible? ndc, rest + rescue StandardError + return false + end + + def validator_for cc + @validators[cc] || default_validator + end + + def default_validator + @default_validator ||= Validator.new + end + + end + +end \ No newline at end of file diff --git a/phony.gemspec b/phony.gemspec index 2904f9a6..2ae5f016 100644 --- a/phony.gemspec +++ b/phony.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |s| s.name = 'phony' - s.version = '1.6.5' + s.version = '1.6.6' s.authors = ['Florian Hanke'] s.email = 'florian.hanke+phony@gmail.com' s.homepage = 'http://github.com/floere/phony' diff --git a/spec/lib/phony/validations_spec.rb b/spec/lib/phony/validations_spec.rb new file mode 100644 index 00000000..bef32761 --- /dev/null +++ b/spec/lib/phony/validations_spec.rb @@ -0,0 +1,43 @@ +# encoding: utf-8 +# +require 'spec_helper' + +describe 'validations' do + + describe 'plausible?' do + + it "is correct" do + Phony.plausible?('0000000').should be_false + end + it "is correct" do + Phony.plausible?('hello').should be_false + end + + it "is correct" do + Phony.plausible?('+41 44 111 22 33').should be_true + end + it "is correct for explicit checks" do + Phony.plausible?('+41 44 111 22 33', cc: '41').should be_true + end + it "is correct for explicit checks" do + Phony.plausible?('+41 44 111 22 33', ndc: '44').should be_true + end + it "is correct for explicit checks" do + Phony.plausible?('+41 44 111 22 33', cc: '1').should be_false + end + it "is correct for explicit checks" do + Phony.plausible?('+41 44 111 22 33', ndc: '43').should be_false + end + + context 'countries' do + + it "is correct for US numbers" do + Phony.plausible?('1-800-692-7753').should be_true + Phony.plausible?('1-911').should be_false + end + + end + + end + +end \ No newline at end of file diff --git a/spec/lib/phony_spec.rb b/spec/lib/phony_spec.rb index 5187236b..5392257a 100644 --- a/spec/lib/phony_spec.rb +++ b/spec/lib/phony_spec.rb @@ -57,9 +57,6 @@ it "should not normalize a number with a correct zero inside" do Phony.normalize('+390909709511').should == '390909709511' end - it "works with completely broken numbers" do - Phony.normalize('0000000').should == '' - end end end