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 +2 -0
- data/README.rdoc +22 -3
- data/Rakefile +2 -2
- data/VERSION.yml +2 -2
- data/craigler.gemspec +6 -3
- data/lib/craigler.rb +10 -4
- data/lib/craigler/constants.rb +4 -2
- data/lib/craigler/search.rb +29 -7
- data/test/craigler_search_test.rb +13 -1
- data/test/craigler_test.rb +26 -4
- data/test/test_helper.rb +8 -1
- metadata +4 -3
data/LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -1,21 +1,40 @@
|
|
1
|
-
=
|
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 =>
|
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 = "
|
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
|
|
data/VERSION.yml
CHANGED
data/craigler.gemspec
CHANGED
@@ -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.
|
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-
|
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.
|
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",
|
data/lib/craigler.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
data/lib/craigler/constants.rb
CHANGED
@@ -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
|
data/lib/craigler/search.rb
CHANGED
@@ -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
|
-
|
18
|
-
|
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..
|
23
|
-
|
24
|
-
@results.push(*
|
25
|
-
break
|
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
|
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
|
data/test/craigler_test.rb
CHANGED
@@ -1,11 +1,33 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
class CraiglerTest < Test::Unit::TestCase
|
4
|
-
|
5
|
-
|
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
|
-
|
9
|
-
|
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
|
data/test/test_helper.rb
CHANGED
@@ -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
|
-
|
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.
|
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-
|
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.
|
62
|
+
rubygems_version: 1.3.5
|
62
63
|
signing_key:
|
63
64
|
specification_version: 3
|
64
65
|
summary: Search API for craigslist
|