scapeshift 1.0.1rg0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,36 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## IntelliJ
17
+ .idea
18
+ *.iml
19
+ .rakeTasks
20
+
21
+ ## PROJECT::GENERAL
22
+ coverage
23
+ rdoc
24
+ pkg
25
+
26
+ ## YARD
27
+ doc/*
28
+ .yardoc
29
+
30
+ ## BUNDLER
31
+ .bundle/*
32
+
33
+ ## PROJECT::SPECIFIC
34
+
35
+ ## FAKEWEB CACHE FILES
36
+ test/fakeweb/*.html
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --protected --private --default-return 'void'
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ source :rubygems
2
+
3
+ gem 'bundler'
4
+
5
+ # Development dependencies
6
+ gem 'rake'
7
+ gem 'jeweler'
8
+ gem 'rdoc'
9
+ gem 'yard'
10
+ gem 'bluecloth'
11
+ gem 'shoulda'
12
+
13
+ # Test dependencies
14
+ gem 'fakeweb'
15
+
16
+ # Runtime dependencies
17
+ gem 'nokogiri'
18
+
data/Gemfile.lock ADDED
@@ -0,0 +1,29 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ bluecloth (2.1.0)
5
+ fakeweb (1.3.0)
6
+ git (1.2.5)
7
+ jeweler (1.6.2)
8
+ bundler (~> 1.0)
9
+ git (>= 1.2.5)
10
+ rake
11
+ nokogiri (1.5.0)
12
+ rake (0.9.2)
13
+ rdoc (3.8)
14
+ shoulda (2.11.3)
15
+ yard (0.7.2)
16
+
17
+ PLATFORMS
18
+ ruby
19
+
20
+ DEPENDENCIES
21
+ bluecloth
22
+ bundler
23
+ fakeweb
24
+ jeweler
25
+ nokogiri
26
+ rake
27
+ rdoc
28
+ shoulda
29
+ yard
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Josh Lindsey
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,76 @@
1
+ Scapeshift
2
+ ==========
3
+
4
+ Scapeshift is a webscraper rubygem designed for the Magic: The Gathering Oracle "Gatherer" card index.
5
+ Since Wizards doesn't want to make an API for this system for various reasons, I've gone ahead and made
6
+ a pseudo-API here.
7
+
8
+ Scapeshift uses the delightful Nokogiri gem to parse and scrape the various Oracle pages, generating
9
+ (most commonly) a SortedSet of Scapeshift::Card objects containing the card data. In the case of expansion sets, formats,
10
+ etc. Scapeshift returns a SortedSet of strings.
11
+
12
+ Usage
13
+ -----
14
+
15
+ Usage is as simple as can be:
16
+
17
+ # Grab the complete list of expansion sets
18
+ @sets = Scapeshift::Crawler.crawl :meta, :type => :sets
19
+
20
+ # Grab the card set for an expansion
21
+ @alara_cards = Scapeshift::Crawler.crawl :cards, :set => 'Shards of Alara'
22
+
23
+ # Grab a single named card
24
+ @card = Scapeshift::Crawler.crawl :single, :name => 'Counterspell'
25
+
26
+ Development
27
+ -----------
28
+
29
+ This gem uses Bundler to manage its dependencies for development:
30
+
31
+ $ sudo gem install bundler
32
+ $ cd /path/to/scapeshift
33
+ $ bundle install
34
+
35
+ Bundler is unlike Rubygems in that it doesn't automagically handle load paths for you. To
36
+ make stuff work, you will need to start a subshell with
37
+
38
+ $ bundle exec bash
39
+
40
+ Replacing `bash` with the shell of your choice, of course.
41
+
42
+ Testing
43
+ -------
44
+
45
+ This gem's tests use fakeweb to mock HTTP connections and speed things up. To run the tests you must first run:
46
+
47
+ rake fakeweb:update
48
+
49
+ This will download the content of the urls needed in tests (and specified in `test/fakeweb.urls`) and store them in
50
+ local files for quick access.
51
+
52
+ Then to run the tests just run
53
+
54
+ rake
55
+
56
+ If you write a test that opens up a new URL the test will automatically fail because FakeWeb is configured to not allow
57
+ actual HTTP connections to open and the new URL is not configured for the cache. To do this just add the new URL in the
58
+ `test/fakeweb.urls` file and update the caches with `rake fakeweb:update`.
59
+
60
+ Documentation
61
+ -------------
62
+
63
+ This gem uses Yardoc syntax for documentation. You can generate these docs
64
+ with `rake yard`. Point any webserver at the `docs/` directory to browse.
65
+
66
+ Simple, with Thin:
67
+
68
+ $ cd /path/to/scapeshift
69
+ $ rake yard
70
+ $ cd docs/
71
+ $ thin -A file -d start
72
+
73
+ Copyright
74
+ ---------
75
+
76
+ Copyright (c) 2010 Josh Lindsey. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,64 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "scapeshift"
8
+ gem.summary = %Q{Magic: The Gathering Oracle web scraper}
9
+ gem.description = %Q{Powers the Scapeshift web API.}
10
+ gem.email = "joshua.s.lindsey@gmail.com"
11
+ gem.homepage = "http://github.com/jlindsey/scapeshift"
12
+ gem.authors = ["Josh Lindsey"]
13
+ gem.add_development_dependency "bundler"
14
+ gem.add_dependency "nokogiri"
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
19
+ end
20
+
21
+ require 'rake/testtask'
22
+ Rake::TestTask.new(:test) do |test|
23
+ test.libs << 'lib' << 'test'
24
+ test.pattern = 'test/**/test_*.rb'
25
+ test.verbose = true
26
+ end
27
+
28
+ begin
29
+ require 'rcov/rcovtask'
30
+ Rcov::RcovTask.new do |test|
31
+ test.libs << 'test'
32
+ test.pattern = 'test/**/test_*.rb'
33
+ test.verbose = true
34
+ end
35
+ rescue LoadError
36
+ task :rcov do
37
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
38
+ end
39
+ end
40
+
41
+ task :test
42
+
43
+ task :default => :test
44
+
45
+ require 'yard'
46
+ YARD::Rake::YardocTask.new
47
+
48
+ desc "Remove docs generated by yard"
49
+ task :clobber_yard do
50
+ puts "rm -rf doc/"
51
+ %x{rm -rf doc/}
52
+ end
53
+
54
+ desc "Searches for all @todo tags and prints them"
55
+ task :todos do
56
+ puts %x{grep -inr "@todo" lib/}
57
+ end
58
+
59
+ namespace :fakeweb do
60
+ task :update do
61
+ require 'test/fakeweb_helper'
62
+ FakeWebHelper.cache_all_urls
63
+ end
64
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.1
data/lib/scapeshift.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'scapeshift/crawler'
2
+
3
+ ##
4
+ # A webscraper gem designed for the Magic: The Gathering
5
+ # Oracle "Gatherer" card index.
6
+ #
7
+ # @author Josh Lindsey
8
+ #
9
+ module Scapeshift; end
@@ -0,0 +1,404 @@
1
+ # encoding: utf-8
2
+ module Scapeshift
3
+
4
+ ##
5
+ # Represents a single Magic: The Gathering card. These are created automatically
6
+ # by the various {Crawlers}, but can be instantiated by end-users if they desire.
7
+ #
8
+ # @author Josh Lindsey
9
+ #
10
+ # @since 0.1.0
11
+ #
12
+ class Card
13
+
14
+ ##
15
+ # Base URI for card images. Interpolated with the "multiverse id" of the
16
+ # card to be imaged.
17
+ Image_URI = 'http://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=%d&type=card'
18
+
19
+ ##
20
+ # List of possible Supertypes a Card can have
21
+ Supertypes = %w(Basic Legendary Snow World)
22
+
23
+ ##
24
+ # List of possible Base Types a Card can have
25
+ Base_Types = %w(Artifact Creature Enchantment Land Planeswalker Instant Sorcery Tribal Plane Vanguard)
26
+
27
+ ## @return [String] The card's name
28
+ attr_accessor :name
29
+
30
+ ## @return [String] The card's multiverse id (ie its id on the Gatherer website)
31
+ attr_accessor :multiverse_id
32
+
33
+ ## @return [String] The mana cost of the card, in the form "2BR"
34
+ attr_accessor :cost
35
+
36
+ ## @return [Array] An array of the card's base types
37
+ attr_accessor :base_types
38
+
39
+ ## @return [Array] An array of the card's subtypes.
40
+ attr_accessor :subtypes
41
+
42
+ ## @return [Array] An array of the card's supertypes.
43
+ attr_accessor :supertypes
44
+
45
+ ## @return [String] Attack power for creature cards
46
+ attr_accessor :pow
47
+
48
+ ## @return [String] Toughness for creature cards
49
+ attr_accessor :tgh
50
+
51
+ ## @return [String] The card's body text
52
+ attr_accessor :text
53
+
54
+ ## @return [String] The card's flavour text
55
+ attr_accessor :flavour_text
56
+
57
+ ## @return [Array [[Set, Rarity]]] The sets and rarities of this card.
58
+ attr_accessor :sets
59
+
60
+ ## @return [String] The interpolated Image_URI string
61
+ attr_accessor :image_uri
62
+
63
+ ## @return [String] The loyalty for planeswalker cards
64
+ attr_accessor :loyalty
65
+
66
+ ## @return [String] The card's artist name
67
+ attr_accessor :artist
68
+
69
+ ## @return [String] The card's number
70
+ attr_accessor :number
71
+
72
+ ##
73
+ # Converts a mana word into its representative cost symbol.
74
+ #
75
+ # @example Blue
76
+ # Scapeshift::Card.cost_symbol_from_str "Blue" # => "U"
77
+ #
78
+ # @example 3 Colorless
79
+ # Scapeshift::Card.cost_symbol_from_str "3" # => "3"
80
+ #
81
+ # @param [String] str The String representation of the cost symbol
82
+ #
83
+ # @return [String] The symbol representing the mana cost
84
+ #
85
+ # @raise [Scapeshift::Errors::UnknownCostSymbol] If an urecognized word is supplied.
86
+ # (eg. "Purple")
87
+ #
88
+ # @author Josh Lindsey
89
+ #
90
+ # @since 0.2.0
91
+ #
92
+ def self.cost_symbol_from_str str
93
+ case str
94
+ # If the input is simply a number, it's already a valid cost symbol.
95
+ when /[\d]+/
96
+ str
97
+ # The Chaos Planechase symbol. I don't actually know
98
+ # what the proper textual representation of this is.
99
+ when '[chaos]'
100
+ str
101
+ # "Variable Colorless" is the word representation of X costs.
102
+ when 'Variable Colorless'
103
+ 'X'
104
+ # Normal colors
105
+ when 'White'
106
+ 'W'
107
+ when 'Red'
108
+ 'R'
109
+ when 'Blue'
110
+ 'U'
111
+ when 'Black'
112
+ 'B'
113
+ when 'Green'
114
+ 'G'
115
+ # Two or colors
116
+ when 'Two or White'
117
+ '(2/W)'
118
+ when 'Two or Red'
119
+ '(2/R)'
120
+ when 'Two or Blue'
121
+ '(2/U)'
122
+ when 'Two or Black'
123
+ '(2/B)'
124
+ when 'Two or Green'
125
+ '(2/G)'
126
+ # Phyrexian colors
127
+ when 'Phyrexian White'
128
+ '(W/P)'
129
+ when 'Phyrexian Red'
130
+ '(R/P)'
131
+ when 'Phyrexian Blue'
132
+ '(U/P)'
133
+ when 'Phyrexian Black'
134
+ '(B/P)'
135
+ when 'Phyrexian Green'
136
+ '(G/P)'
137
+ # Dual Colors
138
+ when 'Black or Red'
139
+ '(B/R)'
140
+ when 'Black or Green'
141
+ '(B/G)'
142
+ when 'Red or Green'
143
+ '(R/G)'
144
+ when 'Red or White'
145
+ '(R/W)'
146
+ when 'Green or White'
147
+ '(G/W)'
148
+ when 'Green or Blue'
149
+ '(G/U)'
150
+ when 'White or Blue'
151
+ '(W/U)'
152
+ when 'White or Black'
153
+ '(W/B)'
154
+ when 'Blue or Black'
155
+ '(U/B)'
156
+ when 'Blue or Red'
157
+ '(U/R)'
158
+ # Snow
159
+ when 'Snow'
160
+ 'S}i'
161
+ # Tap
162
+ when 'Tap'
163
+ 'T'
164
+ # Untap
165
+ when 'Untap'
166
+ 'Q'
167
+ else
168
+ raise Scapeshift::Errors::UnknownCostSymbol.new "Unrecognized cost '#{str}'"
169
+ end
170
+ end
171
+
172
+ ##
173
+ # Instantiate a new Card object.
174
+ #
175
+ # @param [Hash] params Default values for the object.
176
+ # Similar to ActiveRecord::Base.
177
+ #
178
+ # @return [Card] The new Card object
179
+ #
180
+ # @raise [NoMethodError] If a key is passed in for which there is no corresponding
181
+ # setter method.
182
+ #
183
+ # @author Josh Lindsey
184
+ #
185
+ # @since 0.1.0
186
+ #
187
+ def initialize params = {}
188
+ self.supertypes = []
189
+ self.subtypes = []
190
+ self.base_types = []
191
+ self.sets = [[]]
192
+
193
+ return if params.empty?
194
+
195
+ params.each_pair do |var, val|
196
+ method = (var.to_s + "=").to_sym
197
+ self.send method, val
198
+ end
199
+ end
200
+
201
+ ##
202
+ # Interpolates the Image_URI with the "multiverse id".
203
+ #
204
+ # @param [Integer] id The "multiverse id" for this card
205
+ #
206
+ # @author Josh Lindsey
207
+ #
208
+ # @since 0.1.0
209
+ #
210
+ def image_uri_from_id= id
211
+ self.image_uri = Image_URI % id
212
+ end
213
+
214
+ ## To make Hash instantiation more readable
215
+ alias :image_id= :image_uri_from_id=
216
+
217
+ ##
218
+ # Sets the Card's {#subtypes}, {#supertypes}, and {#base_types}
219
+ # from a correctly formatted String.
220
+ #
221
+ # @param [String] types_str The types string
222
+ #
223
+ # @author Josh Lindsey
224
+ #
225
+ # @since 0.1.3
226
+ #
227
+ def types= types_str
228
+ ary = [[], []]
229
+
230
+ # From the Cards crawler
231
+ if types_str.include? ' &mdash; '
232
+ ary = _split_base_and_subtypes types_str, ' &mdash; '
233
+ # Also from the Cards crawler: ' – '
234
+ elsif types_str.include? " \342\200\223 "
235
+ ary = _split_base_and_subtypes types_str, " \342\200\223 "
236
+ # Also from the Cards crawler: ' — '
237
+ elsif types_str.include? " \342\200\224 "
238
+ ary = _split_base_and_subtypes types_str, " \342\200\224 "
239
+
240
+ # Other possible dashes
241
+ # Not seen them on gatherer yet but including them for completion's sake
242
+ elsif types_str.include? " \357\271\230 "
243
+ ary = _split_base_and_subtypes types_str, " \357\271\230 "
244
+ elsif types_str.include? " \357\271\243 "
245
+ ary = _split_base_and_subtypes types_str, " \357\271\243 "
246
+ elsif types_str.include? " \357\274\215 "
247
+ ary = _split_base_and_subtypes types_str, " \357\274\215 "
248
+
249
+ # From manual text input.
250
+ # Note that this is a hyphen. Above is a dash (Alt + - in OS X).
251
+ elsif types_str.include? ' - '
252
+ ary = _split_base_and_subtypes types_str, ' - '
253
+
254
+ # If it doesn't contain one of these delimiters, it has no subtypes
255
+ else
256
+ ary[0] = types_str.split(' ')
257
+ end
258
+
259
+ self.supertypes = ary[0] & Supertypes
260
+ self.base_types = ary[0] & Base_Types
261
+ self.subtypes = ary[1]
262
+ end
263
+
264
+ ##
265
+ # The types of this card in string form.
266
+ #
267
+ # @return [String] The formatted Type string.
268
+ # Should be a valid input for {#types=}
269
+ #
270
+ # @author Josh Lindsey
271
+ #
272
+ # @since 0.1.3
273
+ #
274
+ def types
275
+ type = ''
276
+ unless self.supertypes.empty?
277
+ type << self.supertypes.join(' ')
278
+ type << ' '
279
+ end
280
+
281
+ type << self.base_types.join(' ')
282
+
283
+ unless self.subtypes.empty?
284
+ type << ' - '
285
+ type << self.subtypes.join(' ')
286
+ end
287
+
288
+ type
289
+ end
290
+
291
+ ##
292
+ # The most recent rarity of this card.
293
+ #
294
+ # @return [String] The rarity.
295
+ #
296
+ # @author Josh Lindsey
297
+ #
298
+ # @since 0.1.0
299
+ #
300
+ def rarity
301
+ self.sets.first[1]
302
+ end
303
+
304
+ ##
305
+ # The most recent set this card appeared in.
306
+ #
307
+ # @return [String] The set.
308
+ #
309
+ # @author Josh Lindsey
310
+ #
311
+ # @since 0.1.0
312
+ #
313
+ def set
314
+ self.sets.first[0]
315
+ end
316
+
317
+ ##
318
+ # Set the power and toughness from an Array.
319
+ #
320
+ # @param [Array [Power, Toughness]] pt The power and toughness array
321
+ #
322
+ # @author Josh Lindsey
323
+ #
324
+ # @since 0.1.0
325
+ #
326
+ def pow_tgh=(pt)
327
+ return if pt.nil?
328
+ self.pow = pt[0]
329
+ self.tgh = pt[1]
330
+ end
331
+
332
+ ##
333
+ # Operator method to determine sort order. Sorts based
334
+ # on {#name}.
335
+ #
336
+ # @param [Card] other_card The other Card to compare with
337
+ #
338
+ # @return [Integer] 1, 0, or -1
339
+ #
340
+ # @author Josh Lindsey
341
+ #
342
+ # @since 0.1.2
343
+ #
344
+ def <=> other_card
345
+ self.name <=> other_card.name
346
+ end
347
+
348
+ ##
349
+ # Operator method to determine equality. Does an `==` comparison
350
+ # on each attribute of the Card objects. All must be equal
351
+ # for true.
352
+ #
353
+ # @param [Card] other_card The other Card to compare with
354
+ #
355
+ # @return [Boolean] Whether or not they are equal
356
+ #
357
+ # @author Josh Lindsey
358
+ #
359
+ # @since 0.1.2
360
+ #
361
+ def == other_card
362
+ if self.name == other_card.name and
363
+ self.cost == other_card.cost and
364
+ self.sets == other_card.sets and
365
+ self.image_uri == other_card.image_uri and
366
+ self.multiverse_id == other_card.multiverse_id and
367
+ self.text == other_card.text and
368
+ self.flavour_text == other_card.flavour_text and
369
+ self.types == other_card.types and
370
+ self.loyalty == other_card.loyalty and
371
+ self.artist == other_card.artist and
372
+ self.number == other_card.number
373
+
374
+ return true
375
+ end
376
+
377
+ return false
378
+ end
379
+
380
+ private
381
+
382
+ ##
383
+ # Used by {#types=} to split the types line string into an array
384
+ # of types and subtypes.
385
+ #
386
+ # @param [String] types_str The correctly formatted types line string
387
+ # @param [String] split_on The string that delimits super/base types and subtypes
388
+ #
389
+ # @return [Array] First element is the base (and possibly super) types.
390
+ # Second element is the subtypes.
391
+ #
392
+ # @author Josh Lindsey
393
+ #
394
+ # @since 0.1.3
395
+ #
396
+ def _split_base_and_subtypes types_str, split_on
397
+ ary = types_str.split(split_on)
398
+ ary[0] = ary[0].split(' ')
399
+ ary[1] = ary[1].split(' ')
400
+ ary
401
+ end
402
+ end
403
+ end
404
+