Permalink
Browse files

Refactoring Answer#parser. Now instead of a single parser, Answer#par…

…ser returns an instance of Answer::Parser.

Answer::Parser is basically a proxy class: it holds a parser for any answer part and (in the future) is supposed to negotiate parser responses forwarding each method call to the right parser.
  • Loading branch information...
1 parent 5372ff3 commit c22a1057c79c5fbef269703b511f68c246df979d @weppos committed Sep 23, 2009
View
@@ -5,7 +5,7 @@
* ADDED: whois.denic.de (.de TLD) parser [Aaron Mueller]
-* ADDED: support for multipart answers. This is useful in case of thin servers such as .com or .net because the parser needs to know all different responses in order to load all single scanners.
+* ADDED: introduced support for multipart answers and Parser proxy class. This is useful in case of thin servers such as .com or .net because the parser needs to know all different responses in order to load all single scanners.
* ADDED: whois.crsnic.net (.com, .net, ... TLDs) parser.
View
@@ -14,7 +14,8 @@
#++
-require 'whois/answer/parsers/base'
+require 'whois/answer/parser'
+require 'whois/answer/parser/base'
module Whois
@@ -59,6 +60,18 @@ def eql?(other)
end
+ # Returns the content of this answer as a string.
+ # This method joins all answer parts into a single string
+ # and separates each response with a newline character.
+ #
+ # answer = Whois::Answer.new([Whois::Answer::Part.new("First answer.")])
+ # answer.content
+ # # => "First answer."
+ #
+ # answer = Whois::Answer.new([Whois::Answer::Part.new("First answer."), Whois::Answer::Part.new("Second answer.")])
+ # answer.content
+ # # => "First answer.\nSecond answer."
+ #
def content
@content ||= parts.map { |part| part.response }.join("\n")
end
@@ -90,47 +103,24 @@ def match?(pattern)
!content.match(pattern).nil?
end
-
- # Lazy-loads and returns current answer parser.
- #
- # TODO: actually only the first part is considered.
- # Add support for multi-part answer.
- #
+
+ # Lazy-loads and returns a <tt>Whois::Answer::Parser</tt> proxy for current answer.
def parser
- @parser ||= self.class.parser_klass(parts.first.host).new(self)
+ @parser ||= Parser.new(self)
end
protected
# Delegates all method calls to the internal parser.
def method_missing(method, *args, &block)
- if Parsers::Base.allowed_methods.include?(method)
+ if Parser.allowed_methods.include?(method)
parser.send(method, *args, &block)
else
super
end
end
-
- def self.parser_klass(host)
- file = "whois/answer/parsers/#{host}.rb"
- require file
-
- name = host_to_parser(host)
- Parsers.const_get(name)
-
- rescue LoadError
- raise ParserNotFound,
- "Unable to find a parser for the server `#{host}'"
- end
-
- def self.host_to_parser(host)
- host.to_s.
- gsub(/\./, '_').
- gsub(/(?:^|_)(.)/) { $1.upcase }
- end
-
end
end
View
@@ -0,0 +1,96 @@
+#
+# = Ruby Whois
+#
+# An intelligent pure Ruby WHOIS client.
+#
+#
+# Category:: Net
+# Package:: Whois
+# Author:: Simone Carletti <weppos@weppos.net>
+# License:: MIT License
+#
+#--
+#
+#++
+
+
+module Whois
+ class Answer
+
+ #
+ # = Parser
+ #
+ class Parser
+
+ @@allowed_methods = [
+ :disclaimer,
+ :domain, :domain_id,
+ :referral_whois, :referral_url,
+ :status, :registered?, :available?,
+ :created_on, :updated_on, :expires_on,
+ :registrar, :registrant, :admin, :technical,
+ :nameservers,
+ ]
+
+ def self.allowed_methods
+ @@allowed_methods
+ end
+
+ attr_reader :answer
+
+
+ def initialize(answer)
+ @answer = answer
+ end
+
+ def parsers
+ @parsers ||= init_parsers
+ end
+
+
+ protected
+
+
+ # FIXME: only for now, forwards the request to the first parser.
+ # This is the standard behaviour of the previous implementation.
+ def method_missing(method, *args, &block)
+ if parsers.empty?
+ super
+ else
+ parsers.first.send(method, *args, &block)
+ end
+ end
+
+ # Loops through all answer parts and initializes a parser
+ # for any available part.
+ def init_parsers
+ answer.parts.map { |part| parser_for(part) }
+ end
+
+ def parser_for(part)
+ self.class.parser_klass(part.host).new(part)
+ end
+
+
+ def self.parser_klass(host)
+ file = "whois/answer/parser/#{host}.rb"
+ require file
+
+ name = host_to_parser(host)
+ Parser.const_get(name)
+
+ rescue LoadError
+ raise ParserNotFound,
+ "Unable to find a parser for the server `#{host}'"
+ end
+
+ def self.host_to_parser(host)
+ host.to_s.
+ gsub(/\./, '_').
+ gsub(/(?:^|_)(.)/) { $1.upcase }
+ end
+
+ end
+
+ end
+end
@@ -22,7 +22,7 @@
module Whois
class Answer
- module Parsers
+ class Parser
#
# = Base Answer Parser
@@ -37,33 +37,35 @@ module Parsers
#
class Base
- @@allowed_methods = [
- :disclaimer,
- :domain, :domain_id,
- :referral_whois, :referral_url,
- :status, :registered?, :available?,
- :created_on, :updated_on, :expires_on,
- :registrar, :registrant, :admin, :technical,
- :nameservers,
- ]
-
- def self.allowed_methods
- @@allowed_methods
- end
-
- attr_reader :answer
+ attr_reader :part
- def initialize(answer)
- @answer = answer
+ def initialize(part)
+ @part = part
end
- allowed_methods.each do |method|
+ ::Whois::Answer::Parser.allowed_methods.each do |method|
define_method(method) do
raise NotImplementedError, "You should overwrite this method."
end
end
+
+ # This is an internal method primaly used as a common access point
+ # to get the content to be parsed as a string.
+ #
+ # The main reason behind this method is because I changed the internal
+ # representation of the data to be parsed more than once
+ # and I always had to rewrite all single parsers in order to reflect these changes.
+ # Now, as far as the parser access the data via the content method,
+ # there's no need to change each single implementation in case the content source changes.
+ #
+ # That said, the only constraints about this method is to return the data to be parsed as string.
+ #
+ def content
+ part.response
+ end
+
end
end
@@ -14,12 +14,12 @@
#++
-require 'whois/answer/parsers/base'
+require 'whois/answer/parser/base'
module Whois
class Answer
- module Parsers
+ class Parser
#
# = whois.crsnic.net.rb parser
@@ -105,7 +105,7 @@ def node?(key)
end
def parse
- Scanner.new(answer.parts.first.response).parse
+ Scanner.new(content.to_s).parse
end
@@ -14,12 +14,12 @@
#++
-require 'whois/answer/parsers/base'
+require 'whois/answer/parser/base'
module Whois
class Answer
- module Parsers
+ class Parser
class WhoisDenicDe < Base
def disclaimer
@@ -92,7 +92,7 @@ def ast
end
def parse
- Scanner.new(answer.to_s).parse
+ Scanner.new(content.to_s).parse
end
@@ -14,12 +14,12 @@
#++
-require 'whois/answer/parsers/base'
+require 'whois/answer/parser/base'
module Whois
class Answer
- module Parsers
+ class Parser
#
# = whois.nic.it parser
@@ -145,7 +145,7 @@ def changed?(other)
# The opposite of <tt>changed?</tt>.
def unchanged?(other)
self == other ||
- self.answer.to_s == other.answer.to_s
+ self.content.to_s == other.content.to_s
# (self == other) ||
# (domain == other.domain &&
# created_on == other.created_on &&
@@ -192,7 +192,7 @@ def node?(key)
end
def parse
- Scanner.new(answer.to_s).parse
+ Scanner.new(content.to_s).parse
end
@@ -14,12 +14,12 @@
#++
-require 'whois/answer/parsers/base'
+require 'whois/answer/parser/base'
module Whois
class Answer
- module Parsers
+ class Parser
#
# = whois.publicinterestregistry.net parser
@@ -106,7 +106,7 @@ def changed?(other)
def unchanged?(other)
self == other ||
- self.answer.to_s == other.answer.to_s
+ self.content.to_s == other.content.to_s
end
@@ -152,7 +152,7 @@ def node?(key)
end
def parse
- Scanner.new(answer.to_s).parse
+ Scanner.new(content.to_s).parse
end
@@ -0,0 +1,27 @@
+require 'test_helper'
+require 'whois/answer/parser/base'
+
+class AnswerParserBaseTest < Test::Unit::TestCase
+
+ def setup
+ @klass = Whois::Answer::Parser::Base
+ @part = Whois::Answer::Part.new("This is the response.", "whois.foo.com")
+ end
+
+
+ def test_initialize
+ parser = @klass.new(@part)
+ assert_instance_of @klass, parser
+ end
+
+ def test_initialize_should_require_part
+ assert_raise(ArgumentError) { @klass.new }
+ end
+
+
+ def test_content
+ parser = @klass.new(@part)
+ assert_equal @part.response, parser.content
+ end
+
+end
Oops, something went wrong.

0 comments on commit c22a105

Please sign in to comment.