fuzzyurl-0.8.0/0000755000175000017500000000000012725031151013640 5ustar terceiroterceirofuzzyurl-0.8.0/fuzzyurl.gemspec0000644000175000017500000000133112725031151017115 0ustar terceiroterceirorequire './lib/fuzzyurl/version' Gem::Specification.new do |s| s.name = 'fuzzyurl' s.version = Fuzzyurl::VERSION s.date = Fuzzyurl::VERSION_DATE s.summary = 'A library for non-strict parsing, construction, and wildcard-matching of URLs.' s.homepage = 'https://github.com/gamache/fuzzyurl.rb' s.authors = ['Pete Gamache'] s.email = 'pete@gamache.org' s.files = Dir['lib/**/*'] s.license = 'MIT' s.has_rdoc = true s.require_path = 'lib' s.required_ruby_version = '>= 1.9.3' s.add_development_dependency 'rake', '>= 10.0.4' s.add_development_dependency 'minitest', '>= 4.7.0' s.add_development_dependency 'mocha', '>= 0.13.3' s.add_development_dependency 'pry' end fuzzyurl-0.8.0/test/0000755000175000017500000000000012725031151014617 5ustar terceiroterceirofuzzyurl-0.8.0/test/test_helper.rb0000644000175000017500000000017012725031151017460 0ustar terceiroterceirorequire 'minitest/autorun' require 'minitest/pride' require 'mocha/setup' require 'pp' require 'pry' require 'fuzzyurl' fuzzyurl-0.8.0/test/fuzzyurl_test.rb0000644000175000017500000000567512725031151020132 0ustar terceiroterceirorequire 'test_helper' describe Fuzzyurl do describe 'class methods' do describe 'new' do it 'accepts strings or hashes' do assert_equal(Fuzzyurl.new("example.com:80"), Fuzzyurl.new(hostname: 'example.com', port: '80')) end end describe 'mask' do it 'accepts strings or hashes' do assert_equal(Fuzzyurl.mask("example.com:80"), Fuzzyurl.mask(hostname: 'example.com', port: '80')) end end describe 'to_string' do it 'is delegated' do assert_equal("example.com", Fuzzyurl.to_string(Fuzzyurl.new(hostname: "example.com"))) end end describe 'from_string' do it 'is delegated' do assert_equal(Fuzzyurl.new(hostname: "example.com"), Fuzzyurl.from_string("example.com")) end end describe 'match' do it 'accepts strings or Fuzzyurls' do assert_equal(0, Fuzzyurl.match(Fuzzyurl.mask, "example.com")) end end describe 'matches?' do it 'accepts strings or Fuzzyurls' do assert(Fuzzyurl.matches?(Fuzzyurl.mask, "example.com")) end end describe 'match_scores' do it 'accepts strings or Fuzzyurls' do scores = Fuzzyurl.match_scores(Fuzzyurl.mask, "example.com") assert_equal(8, scores.count) assert_equal(0, scores.values.reduce(:+)) end end describe 'best_match_index' do it 'accepts strings or Fuzzyurls' do assert_equal(1, Fuzzyurl.best_match_index( [Fuzzyurl.mask, "example.com:80", "example.com"], "http://example.com")) end end describe 'best_match' do it 'returns the input object, not always a Fuzzyurl' do assert_equal("example.com:80", Fuzzyurl.best_match( [Fuzzyurl.mask, "example.com:80", "example.com"], "http://example.com")) end end describe 'fuzzy_match' do it 'is delegated' do assert_equal(1, Fuzzyurl.fuzzy_match("a", "a")) end end end # class methods describe 'instance methods' do describe 'to_hash' do it 'works' do assert_equal(Fuzzyurl.new("example.com:8888").to_hash, {protocol: nil, username: nil, password: nil, hostname: 'example.com', port: '8888', path: nil, query: nil, fragment: nil}) end end describe 'with' do it 'creates a new Fuzzyurl object' do fu1 = Fuzzyurl.new(hostname: 'example.com', port: '80') fu2 = fu1.with(port: '8888') assert(fu1 != fu2) assert_equal('example.com', fu1.hostname) assert_equal('example.com', fu2.hostname) assert_equal('80', fu1.port) assert_equal('8888', fu2.port) end end describe 'to_s' do it 'is delegated' do assert_equal("http://example.com", Fuzzyurl.new(protocol: 'http', hostname: 'example.com').to_s) end end end # instance methods end fuzzyurl-0.8.0/test/url_suite_test.rb0000644000175000017500000000105312725031151020215 0ustar terceiroterceirorequire 'test_helper' require 'json' MATCHES = JSON.parse(File.read(File.expand_path("../matches.json", __FILE__))) describe 'URL test suite' do describe 'positive matches' do MATCHES['positive_matches'].each do |(mask, url)| it "'#{mask}' matches '#{url}'" do assert(Fuzzyurl.matches?(mask, url)) end end end describe 'negative matches' do MATCHES['negative_matches'].each do |(mask, url)| it "'#{mask}' does not match '#{url}'" do assert(!Fuzzyurl.matches?(mask, url)) end end end end fuzzyurl-0.8.0/test/fuzzyurl/0000755000175000017500000000000012725031151016531 5ustar terceiroterceirofuzzyurl-0.8.0/test/fuzzyurl/match_test.rb0000644000175000017500000000714412725031151021217 0ustar terceiroterceirorequire 'test_helper' describe Fuzzyurl::Match do describe 'fuzzy_match' do it 'returns 0 for full wildcard' do assert_equal(0, Fuzzyurl::Match.fuzzy_match("*", "lol")) assert_equal(0, Fuzzyurl::Match.fuzzy_match("*", "*")) assert_equal(0, Fuzzyurl::Match.fuzzy_match("*", nil)) end it 'returns 1 for exact match' do assert_equal(1, Fuzzyurl::Match.fuzzy_match('asdf', 'asdf')) end it 'handles *.example.com' do assert_equal(0, Fuzzyurl::Match.fuzzy_match( '*.example.com', 'api.v1.example.com')) assert_equal(nil, Fuzzyurl::Match.fuzzy_match( '*.example.com', 'example.com')) end it 'handles **.example.com' do assert_equal(0, Fuzzyurl::Match.fuzzy_match( '**.example.com', 'api.v1.example.com')) assert_equal(0, Fuzzyurl::Match.fuzzy_match( '**.example.com', 'example.com')) assert_equal(nil, Fuzzyurl::Match.fuzzy_match( '**.example.com', 'zzzexample.com')) end it 'handles path/*' do assert_equal(0, Fuzzyurl::Match.fuzzy_match('path/*', 'path/a/b/c')) assert_equal(nil, Fuzzyurl::Match.fuzzy_match('path/*', 'path')) end it 'handles path/**' do assert_equal(0, Fuzzyurl::Match.fuzzy_match('path/**', 'path/a/b/c')) assert_equal(0, Fuzzyurl::Match.fuzzy_match('path/**', 'path')) assert_equal(nil, Fuzzyurl::Match.fuzzy_match('path/*', 'pathzzz')) end it 'returns nil for bad matches with no wildcards' do assert_equal(nil, Fuzzyurl::Match.fuzzy_match("asdf", "not quite")) end end describe 'match' do fu = Fuzzyurl.new(protocol: "a", username: "b", password: "c", hostname: "d", port: "e", path: "f", query: "g") it 'returns 0 for full wildcard' do assert_equal(0, Fuzzyurl::Match.match(Fuzzyurl.mask, Fuzzyurl.new)) end it 'returns 8 for full exact match' do assert_equal(8, Fuzzyurl::Match.match(fu, fu)) end it 'returns 1 for one exact match' do mask = Fuzzyurl.mask(protocol: "a") assert_equal(1, Fuzzyurl::Match.match(mask, fu)) end it 'infers protocol from port' do mask = Fuzzyurl.mask(port: 80) url = Fuzzyurl.new(protocol: 'http') assert_equal(1, Fuzzyurl::Match.match(mask, url)) url.port = '443' assert_equal(nil, Fuzzyurl::Match.match(mask, url)) end it 'infers port from protocol' do mask = Fuzzyurl.mask(protocol: 'https') url = Fuzzyurl.new(port: '443') assert_equal(1, Fuzzyurl::Match.match(mask, url)) url.protocol = 'http' assert_equal(nil, Fuzzyurl::Match.match(mask, url)) end end describe 'matches?' do it 'returns true for matches' do assert_equal(true, Fuzzyurl::Match.matches?( Fuzzyurl.mask, Fuzzyurl.new)) assert_equal(true, Fuzzyurl::Match.matches?( Fuzzyurl.mask(hostname: "yes"), Fuzzyurl.new(hostname: "yes"))) end it 'returns false for non-matches' do assert_equal(false, Fuzzyurl::Match.matches?( Fuzzyurl.mask(hostname: "yes"), Fuzzyurl.new(hostname: "no"))) end end describe 'match_scores' do it 'has all Fuzzyurl fields' do scores = Fuzzyurl::Match.match_scores(Fuzzyurl.mask, Fuzzyurl.new) assert_equal(scores.keys.sort, Fuzzyurl::FIELDS.sort) end end describe 'best_match_index' do it 'returns the index of the best match' do best = Fuzzyurl.mask("example.com:8888") no_match = Fuzzyurl.mask("example.com:80") url = Fuzzyurl.from_string("http://example.com:8888") assert_equal(1, Fuzzyurl::Match.best_match_index( [Fuzzyurl.mask, best, no_match], url)) end end end fuzzyurl-0.8.0/test/fuzzyurl/strings_test.rb0000644000175000017500000000363012725031151021610 0ustar terceiroterceirorequire 'test_helper' describe Fuzzyurl::Strings do describe 'from_string' do it 'handles simple URLs' do assert(Fuzzyurl::Strings.from_string("http://example.com")) assert(Fuzzyurl::Strings.from_string("ssh://user:pass@host")) assert(Fuzzyurl::Strings.from_string("https://example.com:443/omg/lol")) end it 'rejects bullshit' do assert_equal(nil, Fuzzyurl::Strings.from_string(nil)) assert_equal(nil, Fuzzyurl::Strings.from_string(22)) end it 'handles rich URLs' do fu = Fuzzyurl::Strings.from_string("http://user_1:pass%20word@foo.example.com:8000/some/path?awesome=true&encoding=ebcdic#/hi/mom") assert_equal("http", fu.protocol) assert_equal("user_1", fu.username) assert_equal("pass%20word", fu.password) assert_equal("foo.example.com", fu.hostname) assert_equal("8000", fu.port) assert_equal("/some/path", fu.path) assert_equal("awesome=true&encoding=ebcdic", fu.query) assert_equal("/hi/mom", fu.fragment) end end describe 'to_string' do it 'handles simple URLs' do assert_equal('example.com', Fuzzyurl::Strings.to_string( Fuzzyurl.new(hostname: 'example.com'))) assert_equal('http://example.com', Fuzzyurl::Strings.to_string( Fuzzyurl.new(protocol: 'http', hostname: 'example.com'))) assert_equal('http://example.com/oh/yeah', Fuzzyurl::Strings.to_string( Fuzzyurl.new(path: '/oh/yeah', protocol: 'http', hostname: 'example.com'))) end it 'handles rich URLs' do fu = Fuzzyurl.new( protocol: "https", username: "u", password: "p", hostname: "api.example.com", port: "443", path: "/secret/endpoint", query: "admin=true", fragment: "index" ) assert_equal(Fuzzyurl::Strings.to_string(fu), "https://u:p@api.example.com:443/secret/endpoint?admin=true#index") end end end fuzzyurl-0.8.0/test/fuzzyurl/protocols_test.rb0000644000175000017500000000142212725031151022140 0ustar terceiroterceirorequire 'test_helper' describe Fuzzyurl::Protocols do describe 'get_port' do it 'gets port by protocol' do assert_equal('80', Fuzzyurl::Protocols.get_port('http')) assert_equal('443', Fuzzyurl::Protocols.get_port('https')) assert_equal('22', Fuzzyurl::Protocols.get_port('git+ssh')) assert_equal(nil, Fuzzyurl::Protocols.get_port('hi mom')) assert_equal(nil, Fuzzyurl::Protocols.get_port(nil)) end end describe 'get_protocol' do it 'gets protocol by port' do assert_equal('http', Fuzzyurl::Protocols.get_protocol('80')) assert_equal('http', Fuzzyurl::Protocols.get_protocol(80)) assert_equal(nil, Fuzzyurl::Protocols.get_protocol(nil)) assert_equal(nil, Fuzzyurl::Protocols.get_protocol(-22)) end end end fuzzyurl-0.8.0/test/matches.json0000644000175000017500000000513212725031151017137 0ustar terceiroterceiro{ "version": "0.1.0", "positive_matches": [ [ "*", "http://example.com" ], [ "*", "http://example.com/" ], [ "*", "https://example.com/" ], [ "*", "http://username:password@api.example.com/v1" ], [ "*", "http://example.com/Q@(@*&%&!" ], [ "*", "http://example.com" ], [ "*", "http://user:pass@example.com:12345/some/path%20?query=true&foo=bar#frag" ], [ "http://*", "http://example.com" ], [ "http://*", "http://example.com:80" ], [ "http://*", "http://example.com:8080" ], [ "http://*", "http://example.com/some/path?args=1" ], [ "example.com", "example.com" ], [ "example.com", "example.com:80" ], [ "example.com", "example.com/a/b/c" ], [ "example.com", "example.com:80/a/b/c" ], [ "example.com", "http://example.com" ], [ "example.com", "http://example.com/some/path?args=1" ], [ "example.com", "http://example.com:80/" ], [ "example.com", "http://example.com" ], [ "example.com/a/*", "example.com/a/b/c" ], [ "https://example.com", "example.com:443" ], [ "http://example.com", "example.com:80" ], [ "*.example.com", "api.example.com" ], [ "*.example.com", "xxx.yyy.example.com" ], [ "example.com:8080", "example.com:8080" ], [ "localhost:12345", "localhost:12345" ], [ "user:pass@host", "http://user:pass@host/some/path?q=1" ], [ "*.example.com:80", "http://www.example.com/index.html" ], [ "example.com:443", "https://example.com/" ], [ "svn+ssh://user@example.com", "svn+ssh://user:pass@example.com/some/path" ] ], "negative_matches": [ [ "http://*", "https://example.com" ], [ "https://*", "http://example.com" ], [ "http://example.com", "http://www.example.com" ], [ "*.example.com", "example.com" ], [ "example.com/a/*", "example.com/b/a/b/c" ], [ "example.com/a/*", "foobar.com/a/b/c" ], [ "example.com:888", "example.com:8888" ], [ "user:pass@host", "http://user:@host/some/path?q=1" ], [ "user:pass@host", "http://user@host/some/path?q=1" ] ] } fuzzyurl-0.8.0/.travis.yml0000644000175000017500000000010712725031151015747 0ustar terceiroterceirolanguage: ruby rvm: - 2.3.0 - 2.2.4 - 2.1.8 - 2.0.0 - 1.9.3 fuzzyurl-0.8.0/lib/0000755000175000017500000000000012725031151014406 5ustar terceiroterceirofuzzyurl-0.8.0/lib/fuzzyurl.rb0000644000175000017500000001506012725031151016647 0ustar terceiroterceirorequire 'fuzzyurl/version' require 'fuzzyurl/fields' require 'fuzzyurl/protocols' require 'fuzzyurl/match' require 'fuzzyurl/strings' class Fuzzyurl FIELDS.each {|f| attr_accessor f} # Creates a new Fuzzyurl object from the given params or URL string. # Keys of `params` should be symbols. # # @param params [Hash|String|nil] URL string or parameter hash. # @return [Fuzzyurl] New Fuzzyurl object. def initialize(params={}) p = params.kind_of?(String) ? Fuzzyurl.from_string(params).to_hash : params (FIELDS & p.keys).each do |f| self.send("#{f}=", p[f]) end end # Returns a hash representation of this Fuzzyurl, with one key/value pair # for each of `Fuzzyurl::FIELDS`. # # @return [Hash] Hash representation of this Fuzzyurl. def to_hash FIELDS.reduce({}) do |hash, f| val = self.send(f) val = val.to_s if val hash[f] = val hash end end # Returns a new copy of this Fuzzyurl, with the given params changed. # # @param params [Hash|nil] New parameter values. # @return [Fuzzyurl] Copy of `self` with the given parameters changed. def with(params={}) fu = Fuzzyurl.new(self.to_hash) (FIELDS & params.keys).each do |f| fu.send("#{f}=", params[f].to_s) end fu end # Returns a string representation of this Fuzzyurl. # # @return [String] String representation of this Fuzzyurl. def to_s Fuzzyurl::Strings.to_string(self) end # @private def ==(other) self.to_hash == other.to_hash end class << self # Returns a Fuzzyurl suitable for use as a URL mask, with the given # values optionally set from `params` (Hash or String). # # @param params [Hash|String|nil] Parameters to set. # @return [Fuzzyurl] Fuzzyurl mask object. def mask(params={}) params ||= {} return from_string(params, default: "*") if params.kind_of?(String) m = Fuzzyurl.new FIELDS.each do |f| m.send("#{f}=", params.has_key?(f) ? params[f].to_s : "*") end m end # Returns a string representation of `fuzzyurl`. # # @param fuzzyurl [Fuzzyurl] Fuzzyurl to convert to string. # @return [String] String representation of `fuzzyurl`. def to_string(fuzzyurl) Fuzzyurl::Strings.to_string(fuzzyurl) end # Returns a Fuzzyurl representation of the given URL string. # Any fields not present in `str` will be assigned the value # of `opts[:default]` (defaults to nil). # # @param str [String] String URL to convert to Fuzzyurl. # @param opts [Hash|nil] Options. # @return [Fuzzyurl] Fuzzyurl representation of `str`. def from_string(str, opts={}) Fuzzyurl::Strings.from_string(str, opts) end # Returns an integer representing how closely `mask` matches `url` # (0 means wildcard match, higher is closer), or nil for no match. # # `mask` and `url` may each be Fuzzyurl or String format. # # @param mask [Fuzzyurl|String] URL mask. # @param url [Fuzzyurl|String] URL. # @return [Integer|nil] 0 for wildcard match, 1 for perfect match, or nil. def match(mask, url) m = mask.kind_of?(Fuzzyurl) ? mask : Fuzzyurl.mask(mask) u = url.kind_of?(Fuzzyurl) ? url : Fuzzyurl.from_string(url) Fuzzyurl::Match.match(m, u) end # Returns true if `mask` matches `url`, false otherwise. # # `mask` and `url` may each be Fuzzyurl or String format. # # @param mask [Fuzzyurl|String] URL mask. # @param url [Fuzzyurl|String] URL. # @return [Boolean] Whether `mask` matches `url`. def matches?(mask, url) m = mask.kind_of?(Fuzzyurl) ? m : Fuzzyurl.mask(m) u = url.kind_of?(Fuzzyurl) ? u : Fuzzyurl.from_string(u) m = mask.kind_of?(Fuzzyurl) ? mask : Fuzzyurl.mask(mask) u = url.kind_of?(Fuzzyurl) ? url : Fuzzyurl.from_string(url) Fuzzyurl::Match.matches?(m, u) end # Returns a Hash of match scores for each field of `mask` and # `url`, indicating the closeness of the match. Values are from # `fuzzy_match`: 0 indicates wildcard match, 1 indicates perfect # match, and nil indicates no match. # # `mask` and `url` may each be Fuzzyurl or String format. # # @param mask [Fuzzyurl|String] URL mask. # @param url [Fuzzyurl|String] URL. def match_scores(mask, url) m = mask.kind_of?(Fuzzyurl) ? m : Fuzzyurl.mask(m) u = url.kind_of?(Fuzzyurl) ? u : Fuzzyurl.from_string(u) m = mask.kind_of?(Fuzzyurl) ? mask : Fuzzyurl.mask(mask) u = url.kind_of?(Fuzzyurl) ? url : Fuzzyurl.from_string(url) Fuzzyurl::Match.match_scores(m, u) end # Given an array of URL masks, returns the array index of the one which # most closely matches `url`, or nil if none match. # # `url` and each element of `masks` may be Fuzzyurl or String format. # # @param masks [Array] Array of URL masks. # @param url [Fuzzyurl|String] URL. # @return [Integer|nil] Array index of best-matching mask, or nil for no match. def best_match_index(masks, url) ms = masks.map {|m| m.kind_of?(Fuzzyurl) ? m : Fuzzyurl.mask(m)} u = url.kind_of?(Fuzzyurl) ? url : Fuzzyurl.from_string(url) Fuzzyurl::Match.best_match_index(ms, u) end # Given an array of URL masks, returns the one which # most closely matches `url`, or nil if none match. # # `url` and each element of `masks` may be Fuzzyurl or String format. # # @param masks [Array] Array of URL masks. # @param url [Fuzzyurl|String] URL. # @return [Integer|nil] Best-matching given mask, or nil for no match. def best_match(masks, url) index = best_match_index(masks, url) index && masks[index] end # If `mask` (which may contain * wildcards) matches `url` (which may not), # returns 1 if `mask` and `url` match perfectly, 0 if `mask` and `url` # are a wildcard match, or null otherwise. # # Wildcard language: # # * matches anything # foo/* matches "foo/" and "foo/bar/baz" but not "foo" # foo/** matches "foo/" and "foo/bar/baz" and "foo" # *.example.com matches "api.v1.example.com" but not "example.com" # **.example.com matches "api.v1.example.com" and "example.com" # # Any other form is treated as a literal match. # # @param mask [String] String mask to match with (may contain wildcards). # @param value [String] String value to match. # @returns [Integer|nil] 0 for wildcard match, 1 for perfect match, else nil. def fuzzy_match(mask, value) Fuzzyurl::Match.fuzzy_match(mask, value) end end # class << self end fuzzyurl-0.8.0/lib/fuzzyurl/0000755000175000017500000000000012725031151016320 5ustar terceiroterceirofuzzyurl-0.8.0/lib/fuzzyurl/match.rb0000644000175000017500000001046312725031151017745 0ustar terceiroterceirorequire 'fuzzyurl/protocols' class Fuzzyurl::Match class << self # If `mask` (which may contain * wildcards) matches `url` (which may not), # returns an integer representing how closely they match (higher is closer). # If `mask` does not match `url`, returns null. # # @param mask [Fuzzyurl] Fuzzyurl mask to match with # @param url [Fuzzyurl] Fuzzyurl URL to match # @returns [Fuzzyurl] Fuzzyurl-like object containing match scores def match(mask, url) scores = match_scores(mask, url) return nil if scores.values.include?(nil) scores.values.reduce(:+) end # If `mask` (which may contain * wildcards) matches `url` (which may not), # returns true; otherwise returns false. # # @param mask [Fuzzyurl] Fuzzyurl mask to match with # @param url [Fuzzyurl] Fuzzyurl URL to match # @returns [Fuzzyurl] Fuzzyurl-like object containing match scores def matches?(mask, url) match(mask, url) != nil end # Returns a Fuzzyurl-like object containing values representing how well # different parts of `mask` and `url` match. Values are integers for # matches or null for no match; higher integers indicate a better match. # # @param mask [Fuzzyurl] Fuzzyurl mask to match with # @param url [Fuzzyurl] Fuzzyurl URL to match # @returns [Hash] Hash containing match scores for each field def match_scores(mask, url) url_protocol = url.protocol || Fuzzyurl::Protocols.get_protocol(url.port) url_port = url.port || Fuzzyurl::Protocols.get_port(url.protocol) { protocol: fuzzy_match(mask.protocol, url_protocol), username: fuzzy_match(mask.username, url.username), password: fuzzy_match(mask.password, url.password), hostname: fuzzy_match(mask.hostname, url.hostname), port: fuzzy_match(mask.port, url_port), path: fuzzy_match(mask.path, url.path), query: fuzzy_match(mask.query, url.query), fragment: fuzzy_match(mask.fragment, url.fragment) } end # From a list of Fuzzyurl `masks`, returns the index of the one which best # matches `url`. Returns null if none of `masks` match. # # @param [Array] Array of Fuzzyurl URL mask objects to match with. # @param [Fuzzyurl] Fuzzyurl URL to match. # @returns [Integer|nil] Index of best matching mask, or null if none match. def best_match_index(masks, url) best_index = nil best_score = -1 masks.each_with_index do |mask, i| score = match(mask, url) if score && score > best_score best_score = score best_index = i end end best_index end # If `mask` (which may contain * wildcards) matches `url` (which may not), # returns 1 if `mask` and `url` match perfectly, 0 if `mask` and `url` # are a wildcard match, or null otherwise. # # Wildcard language: # # * matches anything # foo/* matches "foo/" and "foo/bar/baz" but not "foo" # foo/** matches "foo/" and "foo/bar/baz" and "foo" # *.example.com matches "api.v1.example.com" but not "example.com" # **.example.com matches "api.v1.example.com" and "example.com" # # Any other form is treated as a literal match. # # @param mask [String] String mask to match with (may contain wildcards). # @param value [String] String value to match. # @returns [Integer|nil] 0 for wildcard match, 1 for perfect match, else nil. def fuzzy_match(mask, value) return 0 if mask == "*" return 1 if mask == value return nil if !mask || !value if mask.index("**.") == 0 mask_value = mask[3..-1] return 0 if value.end_with?(".#{mask_value}") return 0 if mask_value == value return nil end if mask.index("*") == 0 return 0 if value.end_with?(mask[1..-1]) return nil end rev_mask = mask.reverse rev_value = value.reverse if rev_mask.index("**/") == 0 rev_mask_value = rev_mask[3..-1] return 0 if rev_value.end_with?("/#{rev_mask_value}") return 0 if rev_mask_value == rev_value return nil end if rev_mask.index("*") == 0 return 0 if rev_value.end_with?(rev_mask[1..-1]) return nil end nil end end end fuzzyurl-0.8.0/lib/fuzzyurl/version.rb0000644000175000017500000000010612725031151020327 0ustar terceiroterceiroclass Fuzzyurl VERSION = "0.8.0" VERSION_DATE = "2015-12-25" end fuzzyurl-0.8.0/lib/fuzzyurl/protocols.rb0000644000175000017500000000071512725031151020674 0ustar terceiroterceiroclass Fuzzyurl::Protocols PORTS_BY_PROTOCOL = { 'ssh' => '22', 'http' => '80', 'https' => '443' } PROTOCOLS_BY_PORT = { '22' => 'ssh', '80' => 'http', '443' => 'https' } class << self def get_port(protocol) return nil unless protocol base_protocol = protocol.split('+').last PORTS_BY_PROTOCOL[base_protocol.to_s] end def get_protocol(port) PROTOCOLS_BY_PORT[port.to_s] end end end fuzzyurl-0.8.0/lib/fuzzyurl/strings.rb0000644000175000017500000000263112725031151020340 0ustar terceiroterceirorequire 'fuzzyurl/fields' class Fuzzyurl::Strings REGEX = %r{ ^ (?: (? \* | [a-zA-Z][A-Za-z+.-]+) ://)? (?: (? \* | [a-zA-Z0-9%_.!~*'();&=+$,-]+) (?: : (? \* | [a-zA-Z0-9%_.!~*'();&=+$,-]*))? @ )? (? [a-zA-Z0-9\.\*\-]+?)? (?: : (? \* | \d+))? (? / [^\?\#]*)? ## captures leading / (?: \? (? [^\#]*) )? (?: \# (? .*) )? $ }x class << self def from_string(str, opts={}) return nil unless str.kind_of?(String) default = opts[:default] if m = REGEX.match(str) fu = Fuzzyurl.new Fuzzyurl::FIELDS.each do |f| fu.send("#{f}=", m[f] || default) end fu else raise ArgumentError, "Couldn't parse url string: #{str}" end end def to_string(fuzzyurl) if !fuzzyurl.kind_of?(Fuzzyurl) raise ArgumentError, "`fuzzyurl` must be a Fuzzyurl" end fu = fuzzyurl str = "" str << "#{fu.protocol}://" if fu.protocol str << "#{fu.username}" if fu.username str << ":#{fu.password}" if fu.password str << "@" if fu.username str << "#{fu.hostname}" if fu.hostname str << ":#{fu.port}" if fu.port str << "#{fu.path}" if fu.path str << "?#{fu.query}" if fu.query str << "##{fu.fragment}" if fu.fragment str end end end fuzzyurl-0.8.0/lib/fuzzyurl/fields.rb0000644000175000017500000000022112725031151020106 0ustar terceiroterceiroclass Fuzzyurl FIELDS = [ :protocol, :username, :password, :hostname, :port, :path, :query, :fragment ] end fuzzyurl-0.8.0/.yardopts0000644000175000017500000000005712725031151015510 0ustar terceiroterceiro--markup-provider=redcarpet --markup=markdown fuzzyurl-0.8.0/LICENSE.txt0000644000175000017500000000204212725031151015461 0ustar terceiroterceiroCopyright (c) 2015 Pete Gamache. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. fuzzyurl-0.8.0/Rakefile0000644000175000017500000000076412725031151015314 0ustar terceiroterceirorequire 'rake/testtask' task :default => [:test] Rake::TestTask.new do |t| t.libs << 'test' t.libs << 'test/lib' t.test_files = FileList['test/**/*_test.rb'] #t.warning = true t.verbose = true end task :release do system(<<-EOT) git add lib/fuzzyurl/version.rb git commit -m 'release v#{Fuzzyurl::VERSION}' git push origin git tag v#{Fuzzyurl::VERSION} git push --tags origin gem build fuzzyurl.gemspec gem push fuzzyurl-#{Fuzzyurl::VERSION}.gem EOT end fuzzyurl-0.8.0/README.md0000644000175000017500000000615212725031151015123 0ustar terceiroterceiro# Fuzzyurl [![Build Status](https://travis-ci.org/gamache/fuzzyurl.rb.svg?branch=master)](https://travis-ci.org/gamache/fuzzyurl.rb) Non-strict parsing, composition, and wildcard matching of URLs in Ruby. ## Introduction Fuzzyurl provides two related functions: non-strict parsing of URLs or URL-like strings into their component pieces (protocol, username, password, hostname, port, path, query, and fragment), and fuzzy matching of URLs and URL patterns. Specifically, URLs that look like this: [protocol ://] [username [: password] @] [hostname] [: port] [/ path] [? query] [# fragment] Fuzzyurls can be constructed using some or all of the above fields, optionally replacing some or all of those fields with a `*` wildcard if you wish to use the Fuzzyurl as a URL mask. ## Installation ## Parsing URLs irb> Fuzzyurl.from_string("https://api.example.com/users/123?full=true") #=> # ## Constructing URLs irb> f = Fuzzyurl.new(hostname: "example.com", protocol: "http", port: "8080") irb> f.to_s #=> "http://example.com:8080" ## Matching URLs Fuzzyurl supports wildcard matching: * `*` matches anything, including `null`. * `foo*` matches `foo`, `foobar`, `foo/bar`, etc. * `*bar` matches `bar`, `foobar`, `foo/bar`, etc. Path and hostname matching allows the use of a greedier wildcard `**` in addition to the naive wildcard `*`: * `*.example.com` matches `filsrv-01.corp.example.com` but not `example.com`. * `**.example.com` matches `filsrv-01.corp.example.com` and `example.com`. * `/some/path/*` matches `/some/path/foo/bar` and `/some/path/` but not `/some/path` * `/some/path/**` matches `/some/path/foo/bar` and `/some/path/` and `/some/path` The `Fuzzyurl.mask` function aids in the creation of URL masks. irb> Fuzzyurl.mask #=> # irb> Fuzzyurl.matches?(Fuzzyurl.mask, "http://example.com:8080/foo/bar") #=> true irb> mask = Fuzzyurl.mask(path: "/a/b/**") irb> Fuzzyurl.matches?(mask, "https://example.com/a/b/") #=> true irb> Fuzzyurl.matches?(mask, "git+ssh://jen@example.com/a/b/") #=> true irb> Fuzzyurl.matches?(mask, "https://example.com/a/bar") #=> false `Fuzzyurl.bestMatch`, given a list of URL masks and a URL, will return the given mask which most closely matches the URL: irb> masks = ["/foo/*", "/foo/bar", Fuzzyurl.mask] irb> Fuzzyurl.best_match(masks, "http://example.com/foo/bar") #=> "/foo/bar" If you'd prefer the array index instead of the matching mask itself, use `Fuzzyurl.best_match_index` instead: irb> Fuzzyurl.best_match_index(masks, "http://example.com/foo/bar") #=> 1 ## Authorship and License Fuzzyurl is copyright 2014-2015, Pete Gamache. Fuzzyurl is released under the MIT License. See LICENSE.txt. If you got this far, you should probably follow me on Twitter. [@gamache](https://twitter.com/gamache) fuzzyurl-0.8.0/.gitignore0000644000175000017500000000002412725031151015624 0ustar terceiroterceiro*.gem Gemfile.lock fuzzyurl-0.8.0/Gemfile0000644000175000017500000000004712725031151015134 0ustar terceiroterceirosource 'https://rubygems.org' gemspec