quixoten-craigler 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE CHANGED
@@ -1,3 +1,5 @@
1
+ (MIT License)
2
+
1
3
  Copyright (c) 2009 Devin Christensen
2
4
 
3
5
  Permission is hereby granted, free of charge, to any person obtaining
@@ -1,21 +1,40 @@
1
- = craigler
1
+ = Craigler
2
2
 
3
3
  Search API for craigslist
4
4
 
5
5
  == SYNOPSIS:
6
+ I couldn't decide which API I liked best, so you have two choices.
6
7
 
7
- Craigler.search(:motorcycles, :in => [:utah, :nevada, :arizona], :for => 'Boulevard M50) do |item|
8
+ Craigler.search(:motorcycles, :in => [:utah, :nevada, :arizona], :for => 'Boulevard M50') do |item|
8
9
  puts item.title
9
10
  puts item.url
10
11
  puts item.time
11
12
  end
13
+
14
+ or
12
15
 
13
- Craigler.find('Boulevard M50', :in => [:utah, :nevada, :arizona], :only => :motorcycles) do |item|
16
+ Craigler.find('Boulevard M50', :in => :california, :only => :motorcycles) do |item|
14
17
  puts item.title
15
18
  puts item.url
16
19
  puts item.time
17
20
  end
18
21
 
22
+ You can also create a search object to fetch the results later. When no location or category is given Craigler searches <tt>:all_for_sale_or_wanted</tt> in <tt>:anywhere</tt>
23
+
24
+ search = Craigler::Search.new('Yamaha')
25
+ search.results(:page_limit => 1)
26
+
27
+ Note that additional calls to <tt>search.results()</tt> will always return the same result set unless refresh is forced
28
+ search.results(:refresh => true)
29
+
30
+ === Supported Categories
31
+ <tt>:all_for_sale_or_wanted</tt>, <tt>:art_and_crafts</tt>, <tt>:auto_parts</tt>, <tt>:baby_and_kid_stuff</tt>, <tt>:barter</tt>, <tt>:bicycles</tt>, <tt>:boats</tt>, <tt>:books</tt>, <tt>:business</tt>, <tt>:cars_and_trucks</tt>, <tt>:clothing</tt>, <tt>:collectibles</tt>, <tt>:community</tt>, <tt>:computers_and_tech</tt>, <tt>:electronics</tt>, <tt>:event</tt>, <tt>:farm_and_garden</tt>, <tt>:free_stuff</tt>, <tt>:furniture</tt>, <tt>:games_and_toys</tt>, <tt>:garage_sales</tt>, <tt>:general</tt>, <tt>:gigs</tt>, <tt>:household</tt>, <tt>:housing</tt>, <tt>:items_wanted</tt>, <tt>:jewelry</tt>, <tt>:jobs</tt>, <tt>:materials</tt>, <tt>:media</tt>, <tt>:motorcycles</tt>, <tt>:musical_instruments</tt>, <tt>:personals</tt>, <tt>:photo_and_video</tt>, <tt>:recreational_vehicles</tt>, <tt>:resume</tt>, <tt>:services_offered</tt>, <tt>:sporting_goods</tt>, <tt>:tickets</tt>, <tt>:tools</tt>
32
+
33
+ === Supported Locations
34
+ <tt>:alaska</tt>, <tt>:arizona</tt>, <tt>:arkansas</tt>, <tt>:california</tt>, <tt>:colorado</tt>, <tt>:connecticut</tt>, <tt>:delaware</tt>, <tt>:dc</tt>, <tt>:florida</tt>, <tt>:georgia</tt>, <tt>:hawaii</tt>, <tt>:idaho</tt>, <tt>:illinois</tt>, <tt>:indiana</tt>, <tt>:iowa</tt>, <tt>:kansas</tt>, <tt>:kentucky</tt>, <tt>:louisiana</tt>, <tt>:maine</tt>, <tt>:maryland</tt>, <tt>:mass</tt>, <tt>:michigan</tt>, <tt>:minnesota</tt>, <tt>:mississippi</tt>, <tt>:missouri</tt>, <tt>:montana</tt>, <tt>:nebraska</tt>, <tt>:nevada</tt>, <tt>:n_hampshire</tt>, <tt>:new_jersey</tt>, <tt>:new_mexico</tt>, <tt>:new_york</tt>, <tt>:n_carolina</tt>, <tt>:north_dakota</tt>, <tt>:ohio</tt>, <tt>:oklahoma</tt>, <tt>:oregon</tt>, <tt>:pennsylvania</tt>, <tt>:rhode_island</tt>, <tt>:s_carolina</tt>, <tt>:south_dakota</tt>, <tt>:tennessee</tt>, <tt>:texas</tt>, <tt>:utah</tt>, <tt>:vermont</tt>, <tt>:virginia</tt>, <tt>:washington</tt>, <tt>:west_virginia</tt> <tt>:wisconsin</tt>, <tt>:wyoming</tt>
35
+
36
+ Or use <tt>:anywhere</tt> to search all supported locations.
37
+
19
38
  == REQUIREMENTS:
20
39
 
21
40
  * Hpricot
data/Rakefile CHANGED
@@ -49,8 +49,8 @@ Rake::RDocTask.new do |rdoc|
49
49
  end
50
50
 
51
51
  rdoc.rdoc_dir = 'rdoc'
52
- rdoc.title = "craigler #{version}"
53
- rdoc.rdoc_files.include('README*')
52
+ rdoc.title = "Craigler #{version}"
53
+ rdoc.rdoc_files.include('README.rdoc', 'LICENSE')
54
54
  rdoc.rdoc_files.include('lib/**/*.rb')
55
55
  end
56
56
 
@@ -1,4 +1,4 @@
1
- ---
1
+ ---
2
2
  :major: 1
3
- :minor: 0
3
+ :minor: 1
4
4
  :patch: 0
@@ -1,12 +1,15 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
1
4
  # -*- encoding: utf-8 -*-
2
5
 
3
6
  Gem::Specification.new do |s|
4
7
  s.name = %q{craigler}
5
- s.version = "1.0.0"
8
+ s.version = "1.1.0"
6
9
 
7
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
11
  s.authors = ["Devin Christensen"]
9
- s.date = %q{2009-07-06}
12
+ s.date = %q{2009-08-11}
10
13
  s.email = %q{quixoten@gmail.com}
11
14
  s.extra_rdoc_files = [
12
15
  "LICENSE",
@@ -30,7 +33,7 @@ Gem::Specification.new do |s|
30
33
  s.homepage = %q{http://github.com/quixoten/craigler}
31
34
  s.rdoc_options = ["--charset=UTF-8"]
32
35
  s.require_paths = ["lib"]
33
- s.rubygems_version = %q{1.3.4}
36
+ s.rubygems_version = %q{1.3.5}
34
37
  s.summary = %q{Search API for craigslist}
35
38
  s.test_files = [
36
39
  "test/craigler_search_test.rb",
@@ -5,20 +5,26 @@ require 'craigler/constants'
5
5
  require 'craigler/search'
6
6
 
7
7
  module Craigler
8
- VERSION = '0.1.0'
9
-
10
8
  class CraiglerError < StandardError; end
11
9
  class InvalidCategory < CraiglerError; end
12
10
  class InvalidSearchTerm < CraiglerError; end
13
11
  class InvalidLocation < CraiglerError; end
14
12
 
15
13
  class << self
14
+ # Interface to Search that may or may not be more readable
16
15
  def search(category, options = {})
17
- search = Search.new(options[:for], :in => (options[:in] || :anywhere), :only => category)
16
+ results = Search.new(options[:for], :in => (options[:in] || :anywhere), :only => category).results()
17
+ results.each {|result| yield(result) } if block_given?
18
+ results
18
19
  end
19
20
 
21
+ # Interface to Search that somewhat mimics ActiveRecord#find
22
+ #
23
+ # Supports all the options of Search#new
20
24
  def find(search_term, options = {})
21
- search = Search.new(search_term, options)
25
+ results = Search.new(search_term, options).results()
26
+ results.each {|result| yield(result) } if block_given?
27
+ results
22
28
  end
23
29
  end
24
30
  end
@@ -1,4 +1,6 @@
1
1
  module Craigler
2
+ RESULTS_PER_PAGE = 25 # :nodoc:
3
+
2
4
  LOCATIONS = {
3
5
  :alabama => ['http://auburn.craigslist.org/','http://bham.craigslist.org/','http://columbusga.craigslist.org/','http://dothan.craigslist.org/','http://shoals.craigslist.org/','http://gadsden.craigslist.org/','http://huntsville.craigslist.org/','http://mobile.craigslist.org/','http://montgomery.craigslist.org/','http://tuscaloosa.craigslist.org/'],
4
6
  :alaska => ['http://anchorage.craigslist.org/'],
@@ -51,7 +53,7 @@ module Craigler
51
53
  :west_virginia => ['http://charlestonwv.craigslist.org/','http://huntington.craigslist.org/','http://martinsburg.craigslist.org/','http://morgantown.craigslist.org/','http://parkersburg.craigslist.org/','http://wv.craigslist.org/','http://wheeling.craigslist.org/'],
52
54
  :wisconsin => ['http://appleton.craigslist.org/','http://duluth.craigslist.org/','http://eauclaire.craigslist.org/','http://greenbay.craigslist.org/','http://janesville.craigslist.org/','http://racine.craigslist.org/','http://lacrosse.craigslist.org/','http://madison.craigslist.org/','http://milwaukee.craigslist.org/','http://sheboygan.craigslist.org/','http://wausau.craigslist.org/'],
53
55
  :wyoming => ['http://wyoming.craigslist.org/']
54
- }
56
+ } # :nodoc:
55
57
 
56
58
  CATEGORIES = {
57
59
  :community => 'ccc',
@@ -94,5 +96,5 @@ module Craigler
94
96
  :personals => 'ppp',
95
97
  :resume => 'res',
96
98
  :services_offered => 'bbb'
97
- }
99
+ } # :nodoc:
98
100
  end
@@ -6,6 +6,13 @@ module Craigler
6
6
 
7
7
  attr_reader :search_term, :categories, :locations
8
8
 
9
+ # Creates a wrapper object for a craigslist search
10
+ #
11
+ # === Options
12
+ # [:in]
13
+ # Specifies the location(s) to search in. Defaults to <tt>:anywhere</tt>.
14
+ # [:only]
15
+ # Specifies the category or categories to search in. Defaults to <tt>:all_for_sale_or_wanted</tt>
9
16
  def initialize(search_term, options = {})
10
17
  raise InvalidSearchTerm if search_term.nil? || search_term == ''
11
18
 
@@ -14,15 +21,30 @@ module Craigler
14
21
  _parse_options(options)
15
22
  end
16
23
 
17
- def results(refresh = false)
18
- return @results unless @results.nil? || refresh
24
+ # Returns the results of the search. If this is the first time
25
+ # calling #results then they will be fetched over the internet and cached in the search object.
26
+ #
27
+ # === Options
28
+ # [:page_limit]
29
+ # Maximum number of pages to fetch results from. Defaults to <tt>4</tt>.
30
+ # <b>Note:</b> A location may, and often does, have more than one searchable
31
+ # url assciated with it, e.g., {California}[http://geo.craigslist.org/iso/us/ca]. Because
32
+ # <tt>:page_limit</tt> is applied seperately to each url within the location, searching <tt>:in => :california</tt>
33
+ # with a <tt>:page_limit => 4</tt> could potentially make up to 100 page requests.</em>
34
+ # [:refresh]
35
+ # Set to <tt>true</tt> to force an update across the internet.
36
+ def results(options = {})
37
+ options = { :page_limit => 4, :refresh => false }.merge(options)
38
+ return @results unless @results.nil? || options[:refresh]
39
+
40
+ @results = []
41
+ last_page = options[:page_limit] - 1 # pages start at 0
19
42
 
20
- @results = []
21
43
  _for_each_locations_search_url() do |location, url|
22
- (0..19).each do |page|
23
- items = _extract_items_from_url(location, "#{url}&s=#{page*25}")
24
- @results.push(*items)
25
- break unless items.size == 25
44
+ (0..last_page).each do |page|
45
+ results = _extract_items_from_url(location, "#{url}&s=#{page*25}")
46
+ @results.push(*results)
47
+ break if results.size < RESULTS_PER_PAGE
26
48
  end
27
49
  end
28
50
 
@@ -30,6 +30,12 @@ class CraiglerSearchTest < Test::Unit::TestCase
30
30
  end
31
31
  end
32
32
 
33
+ should "use a default category of :all_for_sale_or_wanted" do
34
+ search = Craigler::Search.new('Buell', :in => :utah)
35
+ assert(search.categories == [:all_for_sale_or_wanted],
36
+ "category was [:#{search.categories.join(", :")}] but should have been [:all_for_sale_or_wanted]")
37
+ end
38
+
33
39
  should "require that the location is valid" do
34
40
  assert_raises(Craigler::InvalidLocation) do
35
41
  Craigler::Search.new('Honda Nighthawk', :in => :invalid)
@@ -45,7 +51,7 @@ class CraiglerSearchTest < Test::Unit::TestCase
45
51
 
46
52
  context "fetching search results" do
47
53
  setup do
48
- @search = Craigler::Search.new('Honda Shadow', :in => :utah, :only => :motorcycles)
54
+ @search = Craigler::Search.new('Honda', :in => :utah, :only => :motorcycles, :page_limit => 1)
49
55
  end
50
56
 
51
57
  should "return an array of hashes" do
@@ -54,5 +60,11 @@ class CraiglerSearchTest < Test::Unit::TestCase
54
60
  assert(results.size > 0, "No results were returned")
55
61
  assert(results.inject(true) {|t,r| t && r.is_a?(Hash)})
56
62
  end
63
+
64
+ should "allow us to limit the number of pages searched" do
65
+ one_page_count = @search.results(:page_limit => 1).size
66
+ two_page_count = @search.results(:page_limit => 2, :refresh => true).size
67
+ assert(one_page_count < two_page_count, "#{one_page_count} is not less than #{two_page_count}")
68
+ end
57
69
  end
58
70
  end
@@ -1,11 +1,33 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class CraiglerTest < Test::Unit::TestCase
4
- should "respond to search" do
5
- assert_respond_to(Craigler, :search)
4
+ context "Search interface" do
5
+ should "respond to search" do
6
+ assert_respond_to(Craigler, :search)
7
+ end
8
+
9
+ should "yield results to a block if given" do
10
+ yielded = false
11
+ Craigler::search(:motorcycles, :in => :utah, :for => 'Yamaha', :page_limit => 1) do
12
+ yielded = true; break;
13
+ end
14
+
15
+ assert(yielded, "results were never yielded to the block")
16
+ end
6
17
  end
7
18
 
8
- should "respond to find" do
9
- assert_respond_to(Craigler, :find)
19
+ context "Find interface" do
20
+ should "respond to find" do
21
+ assert_respond_to(Craigler, :find)
22
+ end
23
+
24
+ should "yield results to a block if given" do
25
+ yielded = false
26
+ Craigler::find('Yamaha', :in => :utah, :only => :motorcycles, :page_limit => 1) do
27
+ yielded = true; break;
28
+ end
29
+
30
+ assert(yielded, "results were never yielded to the block")
31
+ end
10
32
  end
11
33
  end
@@ -1,10 +1,17 @@
1
1
  require 'rubygems'
2
2
  require 'test/unit'
3
3
  require 'shoulda'
4
+ require 'ruby-debug'
4
5
 
5
6
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
7
  $LOAD_PATH.unshift(File.dirname(__FILE__))
8
+
7
9
  require 'craigler'
8
10
 
9
- class Test::Unit::TestCase
11
+ module Craigler
12
+ class Search
13
+ def open(*args)
14
+ @@results_page ||= Kernel::open("http://saltlakecity.craigslist.org/search/sss?query=Honda&format=rss&s=0").read()
15
+ end
16
+ end
10
17
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quixoten-craigler
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Devin Christensen
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-07-06 00:00:00 -07:00
12
+ date: 2009-08-11 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -38,6 +38,7 @@ files:
38
38
  - test/test_helper.rb
39
39
  has_rdoc: false
40
40
  homepage: http://github.com/quixoten/craigler
41
+ licenses:
41
42
  post_install_message:
42
43
  rdoc_options:
43
44
  - --charset=UTF-8
@@ -58,7 +59,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
58
59
  requirements: []
59
60
 
60
61
  rubyforge_project:
61
- rubygems_version: 1.2.0
62
+ rubygems_version: 1.3.5
62
63
  signing_key:
63
64
  specification_version: 3
64
65
  summary: Search API for craigslist