Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: 553a339ab0
Fetching contributors…

Cannot retrieve contributors at this time

file 212 lines (158 sloc) 4.336 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212

module Google
  class Search
    
    #--
    # Mixins
    #++
    
    include Enumerable
    
    #--
    # Constants
    #++
    
    URI = 'http://www.google.com/uds'
    
    #--
    # Exceptions
    #++
    
    class Error < StandardError; end
    
    ##
    # Version. Defaults to 1.0
    
    attr_accessor :version
    
    ##
    # Search type symbol.
    
    attr_accessor :type
    
    ##
    # Offset. Defaults to 0
    
    attr_accessor :offset
    
    ##
    # Language. Defaults to :en
    
    attr_accessor :language
    
    ##
    # Weither or not a search request has been sent.
    
    attr_accessor :sent
    
    ##
    # Query. Defaults to nil
    
    attr_accessor :query
    
    ##
    # API Key. Defaults to :notsupplied
    
    attr_accessor :api_key
    
    ##
    # Size. Defaults to :large
    #
    # - :small = 4
    # - :large = 8
    #
    
    attr_accessor :size
    
    ##
    # Additional options. All those listed above
    # are deleted. The remaining represent query
    # string key / value pairs.
    
    attr_reader :options
    
    ##
    # Initialize search _type_ with _options_. Optionally
    # a block may be passed, and the Search instance will
    # be yielded to it.
    
    def initialize options = {}, &block
      @type = self.class.to_s.split('::').last.downcase.to_sym
      @version = options.delete(:version) || 1.0
      @offset = options.delete(:offset) || 0
      @size = options.delete(:size) || :large
      @language = options.delete(:language) || :en
      @query = options.delete(:query)
      @api_key = options.delete(:api_key) || :notsupplied
      @options = options
      raise Error, 'Do not initialize Google::Search; Use a subclass such as Google::Search::Web' if @type == :search
      yield self if block
    end
    
    ##
    # Set a response _block_ which is called every time
    # #get_response is called. Useful for reporting etc.
    
    def each_response &block
      @each_response = block
    end
    
    ##
    # Iterate each item with _block_.
    
    def each_item &block
      response = self.next.response
      if response.valid?
        response.each { |item| yield item }
        each_item &block
      end
    end
    alias :each :each_item
    
    ##
    # Return all items.
    
    def all_items
      select { true }
    end
    alias :all :all_items
    
    ##
    # Return uri.
    
    def get_uri
      URI + "/G#{@type}Search?" +
        (get_uri_params + options.to_a).
          map { |key, value| "#{key}=#{Search.url_encode(value)}" unless value.nil? }.compact.join('&')
    end
    
    #:nodoc:
    
    def get_uri_params
      validate(:query) { |query| query.respond_to?(:to_str) && !query.to_str.empty? }
      validate(:version) { |version| Numeric === version }
      [[:start, offset],
      [:rsz, size],
      [:hl, language],
      [:key, api_key],
      [:v, version],
      [:q, query]]
    end
    
    ##
    # Prepare for next request.
    
    def next
      @offset += Search.size_for(size) if sent
      self
    end
    
    ##
    # Return raw JSON response string.
    
    def get_raw
      @sent = true
      open(get_uri).read
    end
    
    ##
    # Return hash parsed from the raw JSON response.
    
    def get_hash
      Search.json_decode get_raw
    end
    
    ##
    # Return Response object wrapping the JSON
    # response hash.
    
    def get_response
      raw = get_raw
      hash = Search.json_decode raw
      hash['responseSize'] = size
      response = Response.new hash
      response.raw = raw
      @each_response.call response if @each_response
      response
    end
    alias :response :get_response
    
    ##
    # Return int for size _sym_.
    
    def self.size_for sym
      { :small => 4,
        :large => 8 }[sym]
    end
    
    #:nodoc:
    
    def validate meth, &block
      value = send meth
      raise Error, "invalid #{type} #{meth} #{value.inspect}", caller unless yield value
    end
    
    ##
    # Decode JSON _string_.
    
    def self.json_decode string
      JSON.parse string
    end
    
    ##
    # Url encode _string_.
    
    def self.url_encode string
      string.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/) {
        '%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
      }.tr(' ', '+')
    end
    
  end
end
Something went wrong with that request. Please try again.