gatherer 0.0.1

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/CHANGELOG.md ADDED
File without changes
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Jonan Scheffler
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,25 @@
1
+ _.~'`'~,_.~'`'~,_.~'`'~,_.~'`'~,_.~'`'~,_.~'`'~,_.~'`'~,_.~'`'~,_.~'`'~,_.~'`'~,
2
+
3
+ .,-:::::/ :::. :::::::::::: :: .: .,:::::: :::::::.. .,:::::: :::::::..
4
+ ,;;-'````' ;;`;;;;;;;;;;'''',;; ;;,;;;;'''' ;;;;``;;;; ;;;;'''' ;;;;``;;;;
5
+ [[[ [[[[[[/,[[ '[[, [[ ,[[[,,,[[[ [[cccc [[[,/[[[' [[cccc [[[,/[[['
6
+ "$$c. "$$c$$$cc$$$c $$ "$$$"""$$$ $$"""" $$$$$$c $$"""" $$$$$$c
7
+ `Y8bo,,,o88o888 888, 88, 888 "88o888oo,__ 888b "88bo,888oo,__ 888b "88bo,
8
+ `'YMUP"YMMYMM ""` MMM MMM YMM""""YUMMMMMMM "W" """"YUMMMMMMM "W"
9
+
10
+ `'~,_.~'`'~,_.~'`'~,_.~'`'~,_.~'`'~,_.~'`'~,_.~'`'~,_.~'`'~,_.~'`'~,_.~'`'~,_.~'`
11
+
12
+
13
+ # Gatherer: The Magicking
14
+
15
+ Gatherer is a gem to scrape Magic: The Gathering card data from [gatherer.wizards.com](http://gatherer.wizards.com).
16
+
17
+ To grab a card you'll need the Multiverse ID; these IDs uniquely identify cards within gatherer.
18
+
19
+ client = Gatherer::Client.new
20
+ client.fetch_by_multiverse_id(111)
21
+
22
+ The fetch will give you a complete Gatherer::Card with any data that could be scraped from gatherer.
23
+ If you use a non-existent Multiverse ID you will get a Gatherer::CardNotFound error.
24
+
25
+ If you have any questions or you'd like this gem to do something else feel free to contact me.
@@ -0,0 +1,82 @@
1
+ require 'open-uri'
2
+ require 'nokogiri'
3
+ require 'gatherer/card_parser'
4
+
5
+ module Gatherer
6
+ class Card
7
+ COLOR_SYMBOLS = {
8
+ "Variable Colorless" => "X",
9
+ "White" => "W",
10
+ "Blue" => "U",
11
+ "Black" => "B",
12
+ "Red" => "R",
13
+ "Green" => "G",
14
+ "White or Blue" => "(W/U)",
15
+ "White or Black" => "(W/B)",
16
+ "White or Red" => "(W/R)",
17
+ "White or Green" => "(W/G)",
18
+ "Blue or White" => "(U/W)",
19
+ "Blue or Black" => "(U/B)",
20
+ "Blue or Red" => "(U/R)",
21
+ "Blue or Green" => "(U/G)",
22
+ "Black or White" => "(B/W)",
23
+ "Black or Blue" => "(B/U)",
24
+ "Black or Green" => "(B/G)",
25
+ "Black or Red" => "(B/R)",
26
+ "Red or White" => "(R/W)",
27
+ "Red or Blue" => "(R/U)",
28
+ "Red or Black" => "(R/B)",
29
+ "Red or Green" => "(R/G)",
30
+ "Green or White" => "(G/W)",
31
+ "Green or Blue" => "(G/U)",
32
+ "Green or Black" => "(G/B)",
33
+ "Green or Red" => "(G/R)"
34
+ }
35
+
36
+ attr_reader :title,
37
+ :types,
38
+ :mana_cost,
39
+ :converted_mana_cost,
40
+ :subtypes,
41
+ :text,
42
+ :flavor_text,
43
+ :printings,
44
+ :power,
45
+ :toughness,
46
+ :loyalty,
47
+ :illustrator
48
+
49
+ def initialize(attributes = {})
50
+ @title = attributes[:title]
51
+ @types = attributes[:types]
52
+ @mana_cost = attributes[:mana_cost]
53
+ @converted_mana_cost = attributes[:converted_mana_cost]
54
+ @subtypes = attributes[:subtypes]
55
+ @text = attributes[:text]
56
+ @flavor_text = attributes[:flavor_text]
57
+ @printings = attributes[:printings]
58
+ @power = attributes[:power]
59
+ @toughness = attributes[:toughness]
60
+ @loyalty = attributes[:loyalty]
61
+ @illustrator = attributes[:illustrator]
62
+ end
63
+
64
+ def self.new_from_parser(parser)
65
+ new(
66
+ title: parser.title,
67
+ types: parser.types,
68
+ mana_cost: parser.mana_cost,
69
+ converted_mana_cost: parser.converted_mana_cost,
70
+ subtypes: parser.subtypes,
71
+ text: parser.text,
72
+ flavor_text: parser.flavor_text,
73
+ printings: parser.printings,
74
+ power: parser.power,
75
+ toughness: parser.toughness,
76
+ loyalty: parser.loyalty,
77
+ number: parser.number,
78
+ illustrator: parser.illustrator
79
+ )
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,230 @@
1
+ module Gatherer
2
+ class CardParser
3
+ attr_reader :document
4
+
5
+ SELECTORS = {
6
+ title: 'div#ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_nameRow',
7
+ types: 'div#ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_typeRow',
8
+ cmc: 'div#ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_cmcRow',
9
+ mana: 'div#ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_manaRow',
10
+ subtypes: 'div#ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_typeRow',
11
+ text: 'div#ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_textRow',
12
+ flavor_text: 'div#ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_flavorRow',
13
+ set: 'div#ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_setRow',
14
+ other_sets: 'div#ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_otherSetsRow',
15
+ pt: 'div#ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_ptRow',
16
+ number: 'div#ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_numberRow',
17
+ illustrator: 'div#ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_artistRow'
18
+ }
19
+
20
+ def initialize(html, validate_card_markup = true)
21
+ @document = Nokogiri::HTML(html)
22
+ validate if validate_card_markup
23
+ end
24
+
25
+ def validate
26
+ raise CardNotFound if document.css(SELECTORS[:illustrator]).empty?
27
+ end
28
+
29
+ def title(parsed_text = extract_title)
30
+ parsed_text.strip
31
+ end
32
+
33
+ def extract_title
34
+ row = document.css(SELECTORS[:title])
35
+ title_line = row.css('div.value').text
36
+ end
37
+
38
+ def types(parsed_text = extract_types)
39
+ [parsed_text.strip.split("\u2014").first.split].flatten
40
+ end
41
+
42
+ def extract_types
43
+ row = document.css(SELECTORS[:types])
44
+ type_line = row.css('div.value').text
45
+ end
46
+
47
+ def converted_mana_cost(parsed_text = extract_converted_mana_cost)
48
+ parsed_text.strip.to_i
49
+ end
50
+
51
+ def extract_converted_mana_cost
52
+ row = document.css(SELECTORS[:cmc])
53
+ cmc_line = row.css('div.value').text
54
+ end
55
+
56
+ def mana_cost(parsed_text = extract_mana_cost)
57
+ color_map = Gatherer::Card::COLOR_SYMBOLS
58
+ parsed_text.map { |mana| color_map[mana] ? color_map[mana] : mana }.join
59
+ end
60
+
61
+ def extract_mana_cost
62
+ row = document.css(SELECTORS[:mana])
63
+ mana_cost_line = row.css('div.value').css('img').map { |img| img['alt'] }
64
+ end
65
+
66
+ def subtypes(parsed_text = extract_subtypes)
67
+ if parsed_text.include?("\u2014")
68
+ parsed_text.split("\u2014").last.split(' ').map { |type| type.strip }
69
+ else
70
+ []
71
+ end
72
+ end
73
+
74
+ def extract_subtypes
75
+ row = document.css(SELECTORS[:subtypes])
76
+ row.css('div.value').text
77
+ end
78
+
79
+ def text(parsed_text = extract_text)
80
+ parsed_text.map { |line| line.strip }.compact.join("\n")
81
+ end
82
+
83
+ def extract_text
84
+ row = document.css(SELECTORS[:text]).first
85
+ row.inner_html = replace_mana_symbols(row.inner_html)
86
+ row.css('div.value div.cardtextbox').map(&:text)
87
+ end
88
+
89
+ def replace_mana_symbols(html)
90
+ while image = html.match(/<img.*?alt="([^"]*)"[^>]*>/)
91
+ html.gsub!(image.to_s, "{{#{image.captures.first}}}")
92
+ end
93
+
94
+ html
95
+ end
96
+
97
+ def flavor_text(parsed_text = extract_flavor_text)
98
+ parsed_text.strip
99
+ end
100
+
101
+ def extract_flavor_text
102
+ row = document.css(SELECTORS[:flavor_text])
103
+ row.css('div.cardtextbox').text
104
+ end
105
+
106
+ def parse_printing(printing)
107
+ title = printing.split(' (').first
108
+ Printing.new(
109
+ expansion: Expansion.new(title: title, abbreviation: abbreviation(title)),
110
+ rarity: printing.split(' (').last.chop,
111
+ number: (number if current_printing?(title))
112
+ )
113
+ end
114
+
115
+ def printings(parsed_text = extract_printings)
116
+ parsed_text.map do |printing|
117
+ parse_printing(printing)
118
+ end
119
+ end
120
+
121
+ def extract_printings
122
+ ([extract_current_printing] + extract_other_printings).uniq
123
+ end
124
+
125
+ def current_printing(parsed_text = extract_current_printing)
126
+ parse_printing(parsed_text)
127
+ end
128
+
129
+ def current_printing?(title)
130
+ current_printing_text = extract_current_printing
131
+ current_title = current_printing_text.split(' (').first if current_printing_text
132
+ title == current_title
133
+ end
134
+
135
+ def extract_current_printing
136
+ row = document.css(SELECTORS[:set])
137
+ row.css('img').map { |img| img['title'] }.first
138
+ end
139
+
140
+ def other_printings(parsed_text = extract_other_printings)
141
+ parsed_text.map do |printing|
142
+ Printing.new(
143
+ expansion: Expansion.new(:title => parsed_text.split(' (').first),
144
+ rarity: parsed_text.split(' (').last.chop
145
+ )
146
+ end
147
+ end
148
+
149
+ def extract_other_printings
150
+ row = document.css(SELECTORS[:other_sets])
151
+ row.css('img').map { |img| img['title'] }
152
+ end
153
+
154
+ def abbreviation(title, parsed_text = nil)
155
+ parsed_text ||= extract_abbreviation(title)
156
+ parsed_text.split('&set=').last.split('&').first if parsed_text
157
+ end
158
+
159
+ def extract_abbreviation(title)
160
+ images = document.css(SELECTORS[:set]).css('img')
161
+ images += document.css(SELECTORS[:other_sets]).css('img')
162
+
163
+ images.map do |image|
164
+ image['src'] if image['title'].include?(title)
165
+ end.compact.uniq.first
166
+ end
167
+
168
+ def power(parsed_text = extract_power_toughness)
169
+ parsed_text.split('/').first if parsed_text.include?('/')
170
+ end
171
+
172
+ def toughness(parsed_text = extract_power_toughness)
173
+ parsed_text.split('/').last if parsed_text.include?('/')
174
+ end
175
+
176
+ def extract_power_toughness
177
+ row = document.css(SELECTORS[:pt])
178
+ row.css('div.value').text
179
+ end
180
+
181
+ # gatherer uses the pt row to display loyalty
182
+ def loyalty(parsed_text = extract_power_toughness)
183
+ unless parsed_text.include?('/')
184
+ parsed_text.to_i if parsed_text.to_i > 0
185
+ end
186
+ end
187
+
188
+ def number(parsed_text = extract_number)
189
+ parsed_text.to_i
190
+ end
191
+
192
+ def extract_number
193
+ row = document.css(SELECTORS[:number])
194
+ row.css('div.value').text
195
+ end
196
+
197
+ def illustrator(parsed_text = extract_illustrator)
198
+ parsed_text.strip
199
+ end
200
+
201
+ def extract_illustrator
202
+ row = document.css(SELECTORS[:illustrator])
203
+ row.css('div.value').text
204
+ end
205
+
206
+ def to_hash
207
+ {
208
+ title: title,
209
+ types: types,
210
+ mana_cost: mana_cost,
211
+ converted_mana_cost: converted_mana_cost,
212
+ subtypes: subtypes,
213
+ text: text,
214
+ flavor_text: flavor_text,
215
+ printings: printings.map(&:to_hash),
216
+ power: power,
217
+ toughness: toughness,
218
+ loyalty: loyalty,
219
+ number: number,
220
+ illustrator: illustrator
221
+ }
222
+ end
223
+ end
224
+
225
+ class CardNotFound < StandardError
226
+ def initialize
227
+ super("Could not find card.")
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,24 @@
1
+ require 'open-uri'
2
+ require 'gatherer/scraper'
3
+ require 'gatherer/card_parser'
4
+
5
+ module Gatherer
6
+ class Client
7
+ def fetch_by_multiverse_id(id)
8
+ card_from(scraper(id))
9
+ end
10
+
11
+ def scraper(id)
12
+ Gatherer::Scraper.new(multiverse_id: id)
13
+ end
14
+
15
+ def page_from(scraper)
16
+ open(scraper.url)
17
+ end
18
+
19
+ def card_from(scraper)
20
+ parser = Gatherer::CardParser.new(page_from(scraper))
21
+ Card.new_from_parser(parser)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ module Gatherer
2
+ class Expansion
3
+ attr_reader :title, :abbreviation
4
+
5
+ def initialize(attributes)
6
+ @title = attributes[:title]
7
+ @abbreviation = attributes[:abbreviation]
8
+ end
9
+
10
+ def to_hash
11
+ {
12
+ title: @title,
13
+ abbreviation: @abbreviation
14
+ }
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ module Gatherer
2
+ class Printing
3
+ attr_reader :expansion, :rarity, :number
4
+
5
+ def initialize(attributes)
6
+ @expansion = attributes[:expansion]
7
+ @rarity = attributes[:rarity]
8
+ @number = attributes[:number]
9
+ end
10
+
11
+ def to_hash
12
+ {
13
+ expansion: @expansion.to_hash,
14
+ rarity: @rarity,
15
+ number: @number
16
+ }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,35 @@
1
+ module Gatherer
2
+ class Scraper
3
+ BASE_PATH = "http://gatherer.wizards.com"
4
+
5
+ def initialize(params = {})
6
+ @query = ''
7
+ @subpath = ''
8
+
9
+ options = params.map do |k,v|
10
+ send(k, v)
11
+ end
12
+
13
+ if options
14
+ @query = URI.encode(options.join('&'))
15
+ end
16
+ end
17
+
18
+ def url
19
+ BASE_PATH + @subpath + (@query.empty? ? '' : '?' + @query)
20
+ end
21
+
22
+ def set(name)
23
+ "set=[#{name}]"
24
+ end
25
+
26
+ def page(number)
27
+ "page=#{number}"
28
+ end
29
+
30
+ def multiverse_id(id)
31
+ @subpath = "/Pages/Card/Details.aspx"
32
+ "multiverseid=#{id}"
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ module Gatherer
2
+ VERSION = "0.0.1"
3
+ end
data/lib/gatherer.rb ADDED
@@ -0,0 +1,6 @@
1
+ require "gatherer/scraper"
2
+ require "gatherer/card"
3
+ require 'gatherer/card_parser'
4
+ require 'gatherer/printing'
5
+ require 'gatherer/expansion'
6
+ require 'gatherer/client'
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gatherer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jonan Scheffler
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-07 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: httparty
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 0.9.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 0.9.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: nokogiri
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 1.5.5
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 1.5.5
46
+ description: ! 'Gatherer is a gem to scrape Magic: The Gathering cards.'
47
+ email:
48
+ - jonanscheffler@gmail.com
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - lib/gatherer/card.rb
54
+ - lib/gatherer/card_parser.rb
55
+ - lib/gatherer/client.rb
56
+ - lib/gatherer/expansion.rb
57
+ - lib/gatherer/printing.rb
58
+ - lib/gatherer/scraper.rb
59
+ - lib/gatherer/version.rb
60
+ - lib/gatherer.rb
61
+ - LICENSE
62
+ - CHANGELOG.md
63
+ - README.md
64
+ homepage: http://github.com/1337807/gatherer
65
+ licenses: []
66
+ post_install_message:
67
+ rdoc_options: []
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ! '>='
80
+ - !ruby/object:Gem::Version
81
+ version: 1.3.6
82
+ requirements: []
83
+ rubyforge_project:
84
+ rubygems_version: 1.8.23
85
+ signing_key:
86
+ specification_version: 3
87
+ summary: ! 'Gatherer: The Magicking'
88
+ test_files: []