mtg-card-finder 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: fdde645b062acfb7c6bd68782007c76ec0aaa3ce
4
+ data.tar.gz: 64fed502a10a82fe590dd969773b63914310f1d3
5
+ SHA512:
6
+ metadata.gz: 0a0e8791896966c8d54bec61b4439227f85055a062f4f2078664447bb42b1ccce9b878ca41f9f130209543135748c350efb437f8f7c2f6213f44301db4a2cd91
7
+ data.tar.gz: b16f5dba901adf924d286e6d23210d14e00e42979d03199f4abb893f37ac93241101a1a2e3d11174917fd0a14b0188631c27b47902ef17e2a23f74e028f37150
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at gongora.animations@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in mtg-card-finder.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Juan Gongora
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,69 @@
1
+ # Mtg-Card-Finder
2
+
3
+ Welcome to MTG Card Finder! Find the highest rising/falling card prices on the Magic the Gathering open market.
4
+ Updated daily, and able to save your search into a local .csv file for your own personal logging/card hunting.
5
+
6
+ Currently logs Standard and Modern formats only.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'mtg-card-finder'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install mtg-card-finder
23
+
24
+ ## Usage
25
+ Type the below on your terminal to begin the app:
26
+
27
+ $ mtg-card-finder
28
+
29
+ When the application begins, choose between four different pricing options:
30
+
31
+ [1] Standard: rising cards today
32
+ [2] Modern: rising cards today
33
+ [3] Standard: crashing cards today
34
+ [4] Modern: crashing cards today
35
+
36
+ After you have made your choice the app will load a list of the top 40-50 for the day.
37
+
38
+ Each card will display its 'name', what card 'set' it's from, the current 'market value',
39
+ and the amount that it has 'raised' or 'fallen' for the day.
40
+
41
+ You will then be asked for four more additional options:
42
+
43
+ [1] search for a different format's market?
44
+ [2] save the current card search listing into a CSV file?
45
+ [3] purchase one of the queried cards in the open market?
46
+ [4] exit the program?
47
+
48
+ Option 1 will let you search for another of the initially provided formats at the start of the application.
49
+ Option 2 will locally save the queried listing into a .csv file.
50
+ Option 3 will provide you with a url link that directs you to eBay's lowest priced bids for the chosen card.
51
+ Option 4 exits the application.
52
+
53
+ ## Development
54
+
55
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
56
+
57
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
58
+
59
+ ## Contributing
60
+
61
+ Bug reports and pull requests are welcome on GitHub at https://github.com/JuanGongora/mtg-card-finder. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
62
+
63
+ ## Bugs
64
+
65
+ Current bugs: https://github.com/JuanGongora/mtg-card-finder/issues
66
+
67
+ ## License
68
+
69
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,14 @@
1
+ require "bundler/gem_tasks"
2
+ require_relative 'config/environment' #everything is being operated from within here
3
+
4
+ task :default => :spec
5
+
6
+ task :console do #cutsom console initialization for testing
7
+
8
+ def reload! #lets me load all my files again if I make a change
9
+ load_all 'lib'
10
+ end
11
+
12
+ Pry.start #allows pry to start for me to fiddle around with all my methods
13
+
14
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require_relative "../lib/mtg_card_finder"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/mtg_card_finder'
4
+
5
+ MTGCardFinder::CLI.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,10 @@
1
+ require 'open-uri'
2
+ require 'sqlite3'
3
+ require "tco"
4
+ require "mechanize"
5
+ require "nokogiri"
6
+ require "require_all"
7
+
8
+ DB = {:conn => SQLite3::Database.new("db/cards.db")} #this gives me validation to reach the database that module Persistable interacts with
9
+
10
+ require_all 'lib/mtg_card_finder' #this allows me to simultaneously require everything in lib/mtg_card_finder
File without changes
@@ -0,0 +1,4 @@
1
+ require_relative '../config/environment'
2
+
3
+ module MTGCardFinder
4
+ end
@@ -0,0 +1,77 @@
1
+ class MTGCardFinder::CLI
2
+
3
+ def self.start
4
+ Parser.reset_query_info
5
+ puts "Powered by MTG$ (mtgprice.com)"; sleep(0.5);
6
+ puts "-------------------------------------------------"
7
+ puts "Please select your price trend Format:"
8
+ puts "-------------------------------------------------"
9
+ puts "#{"|Last Update|".fg COLORS[6]}#{Parser.update_date}"
10
+ puts "-------------------------------------------------"
11
+ self.set_choosing
12
+ end
13
+
14
+ def self.set_text
15
+ puts "#{"[1]".fg COLORS[3]} Standard: #{"rising".fg COLORS[4]} cards today"
16
+ puts "#{"[2]".fg COLORS[3]} Modern: #{"rising".fg COLORS[4]} cards today"
17
+ puts "#{"[3]".fg COLORS[3]} Standard: #{"crashing".fg COLORS[6]} cards today"
18
+ puts "#{"[4]".fg COLORS[3]} Modern: #{"crashing".fg COLORS[6]} cards today"
19
+ puts "-------------------------------------------------"
20
+ end
21
+
22
+ def self.options_text
23
+ puts "Would you like to?"
24
+ puts "#{"[1]".fg COLORS[3]} search for a different format's market?"
25
+ puts "#{"[2]".fg COLORS[3]} save the current card search listing into a CSV file?"
26
+ puts "#{"[3]".fg COLORS[3]} purchase one of the queried cards in the open market?"
27
+ puts "#{"[4]".fg COLORS[3]} exit the program?"
28
+ puts "-------------------------------------------------"
29
+ end
30
+
31
+ def self.set_choosing
32
+ self.set_text
33
+ self.set_input
34
+ Parser.scrape_cards
35
+ Parser.display_cards
36
+ puts ""
37
+ puts "-------------------------------------------------"
38
+ puts ""
39
+ self.options_text
40
+ self.options_input
41
+ end
42
+
43
+ def self.set_input
44
+ sleep(1)
45
+ puts "Please type out the #{"number".fg COLORS[3]} of the format you would like to see from above..."
46
+ Parser.select_format
47
+ end
48
+
49
+ def self.options_input
50
+ input = gets.strip.to_i
51
+ if input == 1
52
+ puts "Please select your price trend Format:"
53
+ self.set_choosing
54
+ elsif input == 2
55
+ Parser.csv
56
+ sleep(2)
57
+ self.options_text
58
+ self.options_input
59
+ elsif input == 3
60
+ puts "Please type out the #{"number".fg COLORS[4]} from one of the above searched cards:"
61
+ Parser.purchase
62
+ sleep(2.5)
63
+ self.options_text
64
+ self.options_input
65
+ elsif input == 4
66
+ puts ""
67
+ puts ""
68
+ puts "Thank you for using #{"MTG".fg COLORS[6]} #{"CARD".fg COLORS[5]} #{"FINDER".fg COLORS[4]}"
69
+ puts "-----------------------------------"
70
+ exit
71
+ else
72
+ puts "That is not a valid option"
73
+ self.options_input
74
+ end
75
+ end
76
+
77
+ end
@@ -0,0 +1,49 @@
1
+ #creating color configurations for the gem tco
2
+ conf = Tco.config
3
+ conf.names["purple"] = "#622e90"
4
+ conf.names["dark-blue"] = "#2d3091"
5
+ conf.names["blue"] = "#42cbff"
6
+ conf.names["green"] = "#59ff00"
7
+ conf.names["yellow"] = "#fdea22"
8
+ conf.names["orange"] = "#f37f5a"
9
+ conf.names["red"] = "#ff476c"
10
+ conf.names["light_purp"] = "#4d5a75"
11
+ Tco.reconfigure conf
12
+
13
+ #supplementing the color configurations into an array constant
14
+ COLORS = ["purple", "dark-blue", "blue", "green", "yellow", "orange", "red", "light_purp"]
15
+
16
+ mtg = <<-EOS
17
+ BB BB BBBBBBBBB BBBBBBBBB
18
+ BBBB BBBB BBB BBBB BBB
19
+ BBBBBBBBBBBBBB BBB BBB
20
+ BBBB BB BBBB BBB BBB BBBBBB
21
+ BBB BBB BBB BBBB BBB
22
+ BBBBBB BBBBBB BBB BBBBBBBBBBB
23
+ EOS
24
+
25
+ card = <<-EOS
26
+
27
+ BBBBBBB BBBB BBBBBBB BBBBBBB
28
+ BBB BB BB BB BBB BB BBB
29
+ BBB BBB BBB BB BBB BB BBB
30
+ BBB BBBBBBBBBB BBBBBB BB BBB
31
+ BBB BBB BBBB BB BBB BB BBB
32
+ BBBBBB BBB BBBB BB BBB BBBBBBB
33
+ EOS
34
+
35
+ finder = <<-EOS
36
+
37
+ BBBBBBBBBB BB BBB BB BBBBBBB BBBBBBB BBBBBBB
38
+ BB BB BB BB BB BB BBB BB BB BBB
39
+ BBBBBBBBB BB BB BB BB BB BBB BBBBB BBBBBB
40
+ BB BB BB BB BB BB BBB BB BB BB
41
+ BB BB BB BB BB BB BBB BB BB BB
42
+ BB BB BB BBBB BBBBBBBB BBBBBBB BB BB
43
+ EOS
44
+
45
+ puts ""
46
+ print mtg.fg "red"; sleep(1);
47
+ print card.fg "orange"; sleep(1);
48
+ print finder.fg "yellow"; sleep(1);
49
+ puts ""
@@ -0,0 +1,213 @@
1
+ #a dynamic module that contains data that can be re-used, hence the name
2
+ module Persistable
3
+
4
+ module ClassMethods
5
+
6
+ def table_name
7
+ "#{self.to_s.downcase}s"
8
+ end
9
+
10
+ #this method will dynamically create a new instance with the assigned attrs and values
11
+ #by doing mass assignment of the hash's key/value pairs
12
+ def create(attributes_hash)
13
+ #the tap method allows preconfigured methods and values to
14
+ #be associated with the instance during instantiation while also automatically returning
15
+ #the object after its creation is concluded.
16
+ self.new.tap do |card|
17
+ attributes_hash.each do |key, value|
18
+ #sending the new instance the key name as a setter with the value
19
+ card.send("#{key}=", value)
20
+ #string interpolation is used as the method doesn't know the key name yet
21
+ #but an = sign is implemented into the string in order to asssociate it as a setter
22
+ end
23
+ #saves the new instance into the database
24
+ card.save
25
+ end
26
+ end
27
+
28
+ def create_table
29
+ sql = <<-SQL
30
+ CREATE TABLE IF NOT EXISTS #{self.table_name} ( #{self.create_sql} )
31
+ SQL
32
+
33
+ DB[:conn].execute(sql)
34
+ end
35
+
36
+ def remove_table
37
+ sql = <<-SQL
38
+ DROP TABLE IF EXISTS #{self.table_name}
39
+ SQL
40
+
41
+ DB[:conn].execute(sql)
42
+ end
43
+
44
+ def table_empty?
45
+ sql = <<-SQL
46
+ SELECT * FROM #{self.table_name}
47
+ SQL
48
+
49
+ table = DB[:conn].execute(sql)
50
+ check = table.empty?
51
+ if check == true
52
+ true
53
+ end
54
+ end
55
+
56
+ def table_rows
57
+ sql = <<-SQL
58
+ SELECT COUNT(*) FROM #{self.table_name}
59
+ SQL
60
+
61
+ table = DB[:conn].execute(sql)
62
+ rows = table.flatten.join.to_i
63
+ rows
64
+ end
65
+
66
+ def make_csv_file
67
+ #collects everything in sql table
68
+ rows = DB[:conn].execute("SELECT * FROM #{self.table_name}")
69
+ date = "#{Time.now}"[0..9].gsub!("-", "_")
70
+ #naming the csv file with today's date
71
+ fname = "#{self}_#{date}.csv"
72
+ #collecting the table's column names
73
+ col_names = "#{self.attributes.keys.join(", ")} \n"
74
+ unless File.exists? fname
75
+ #opening the csv file to write data into
76
+ File.open(fname, 'w') do |ofile|
77
+ #first row will be column names
78
+ ofile << col_names
79
+ rows.each_with_index do |value, index|
80
+ #iterates through all the rows values to replace commas so as to avoid line breaking errors
81
+ value.each { |find| find.gsub!(", ", "_") if find.is_a?(String) }
82
+ #pushing each array within rows as a newline into csv while removing nil values
83
+ ofile << "#{rows[index].compact.join(", ")} \n"
84
+ end
85
+ sleep(1 + rand)
86
+ end
87
+ end
88
+ end
89
+
90
+ def buy_link(id)
91
+ name = self.find(id)
92
+ begin
93
+ #collect the instant's values as a string
94
+ word = name.card + " " + name.sets
95
+ rescue
96
+ #instead of getting an undefined method error in .card & .sets for nil:NilClass
97
+ #just re-run method until user sets it to a true value
98
+ Parser.purchase
99
+ else
100
+ #replace whitespace chars
101
+ word.gsub!(/\s+/m, '%20')
102
+ #create url for purchasing the chosen id card
103
+ buy = "http://www.ebay.com/sch/?_nkw=#{word}&_sacat=0".fg COLORS[3]
104
+ puts ""
105
+ puts "Please highlight, right click and copy the #{"url".fg COLORS[3]} below and paste it to your preferred browser:"
106
+ puts "--------------------------------------------------------------------------------------------"
107
+ puts ""
108
+ puts buy
109
+ puts ""
110
+ end
111
+ end
112
+
113
+ def find(id)
114
+ sql = <<-SQL
115
+ SELECT * FROM #{self.table_name} WHERE id=(?)
116
+ SQL
117
+
118
+ row = DB[:conn].execute(sql, id)
119
+ #if a row is actually returned i.e. the id actually exists
120
+ if row.first
121
+ self.reify_from_row(row.first)
122
+ #using .first array method to return only the first nested array
123
+ #that is taken from self.reify_from_row(row) which is the resulting id of the query
124
+ else
125
+ puts "This card does not exist"
126
+ end
127
+ end
128
+
129
+ #opposite of abstraction is reification i.e. I'm getting the raw data of these variables
130
+ def reify_from_row(row)
131
+ #the tap method allows preconfigured methods and values to
132
+ #be associated with the instance during instantiation while also automatically returning
133
+ #the object after its creation is concluded.
134
+ self.new.tap do |card|
135
+ self.attributes.keys.each.with_index do |key, index|
136
+ #sending the new instance the key name as a setter with the value located at the 'row' index
137
+ card.send("#{key}=", row[index])
138
+ #string interpolation is used as the method doesn't know the key name yet
139
+ #but an = sign is implemented into the string in order to asssociate it as a setter
140
+ end
141
+ end
142
+ end
143
+
144
+ def create_sql
145
+ #will apply the column names ('key') and their schemas ('value') into sql strings without having to hard code them
146
+ #the collect method returns the revised array and then we concatenate it into a string separating the contents with a comma
147
+ self.attributes.collect {|key, value| "#{key} #{value}"}.join(", ")
148
+ end
149
+
150
+ def attributes_names_insert_sql
151
+ #same idea as self.create_sql only it's returning the 'key' for sql insertions
152
+ self.attributes.keys[1..-1].join(", ")
153
+ end
154
+
155
+ def question_marks_insert_sql
156
+ #returns the number of key-value pairs in the hash minus one for the 'id'
157
+ questions = self.attributes.keys.size-1
158
+ #converts them into '?' array that is then turned into comma separated string
159
+ questions.times.collect {"?"}.join(", ")
160
+ end
161
+
162
+ def sql_columns_to_update
163
+ #returns the number of keys in the hash minus one for the 'id'
164
+ columns = self.attributes.keys[1..-1]
165
+ #converts them into 'attribute=(?)' array that is then turned into comma separated string
166
+ columns.collect {|attr| "#{attr}=(?)"}.join(", ")
167
+ end
168
+ end
169
+
170
+ module InstanceMethods
171
+
172
+ def save
173
+ #if the card has already been saved, then call update method
174
+ persisted? ? update : insert
175
+ #if not call insert method instead
176
+ end
177
+
178
+ def attribute_values_for_sql_check
179
+ self.class.attributes.keys[1..-1].collect {|attr_names| self.send(attr_names)}
180
+ #I go through the key names (minus 'id') and return an array containing their values for the recieving instance
181
+ #basically like getting an array of getter methods for that instance
182
+ end
183
+
184
+ def persisted?
185
+ #the '!!' double bang converts object into a truthy value statement
186
+ !!self.id
187
+ end
188
+
189
+ def update
190
+ #updates by the unique identifier of 'id'
191
+ sql = <<-SQL
192
+ UPDATE #{self.class.table_name} SET #{self.class.sql_columns_to_update} WHERE id=(?)
193
+ SQL
194
+
195
+ #using splat operator to signify that there may be more than one argument in terms of attr_readers
196
+ DB[:conn].execute(sql, *attribute_values_for_sql_check, self.id)
197
+ end
198
+
199
+ def insert
200
+ sql = <<-SQL
201
+ INSERT INTO #{self.class.table_name} (#{self.class.attributes_names_insert_sql}) VALUES (#{self.class.question_marks_insert_sql})
202
+ SQL
203
+
204
+ #using splat operator to signify that there may be more than one argument in terms of attr_readers
205
+ DB[:conn].execute(sql, *attribute_values_for_sql_check)
206
+ #after inserting the card to the database, I want to get the primary key that is auto assigned to it
207
+ #from sql and set it to the instance method 'id' of this very instance variable.
208
+ self.id = DB[:conn].execute("SELECT last_insert_rowid() FROM #{self.class.table_name}")[0][0]
209
+ #returns first array with the first value of the array (i.e. index 0)
210
+ end
211
+ end
212
+
213
+ end
@@ -0,0 +1,118 @@
1
+ class MTG
2
+ attr_accessor :card, :sets, :market_price, :price_fluctuate, :image
3
+ @@modern_up = []
4
+ @@modern_down = []
5
+ @@standard_up = []
6
+ @@standard_down = []
7
+ @@temp_array = []
8
+
9
+ ATTRIBUTES = [
10
+ "Card:",
11
+ "Set:",
12
+ "Market Value:",
13
+ "Rise/Fall amount:",
14
+ "Image URL:"
15
+ ]
16
+
17
+ #new instance will be created with already assigned values to MTG attrs
18
+ def initialize(attributes)
19
+ attributes.each {|key, value| self.send("#{key}=", value)}
20
+ end
21
+
22
+ def save_modern_up
23
+ @@modern_up << self
24
+ end
25
+
26
+ def save_modern_down
27
+ @@modern_down << self
28
+ end
29
+
30
+ def save_standard_up
31
+ @@standard_up << self
32
+ end
33
+
34
+ def save_standard_down
35
+ @@standard_down << self
36
+ end
37
+
38
+ def self.create_modern_up(attributes)
39
+ #allows cards instance to auto return thanks to tap implementation
40
+ cards = MTG.new(attributes).tap {|card| card.save_modern_up}
41
+ end
42
+
43
+ def self.create_modern_down(attributes)
44
+ cards = MTG.new(attributes).tap {|card| card.save_modern_down}
45
+ end
46
+
47
+ def self.create_standard_up(attributes)
48
+ cards = MTG.new(attributes).tap {|card| card.save_standard_up}
49
+ end
50
+
51
+ def self.create_standard_down(attributes)
52
+ cards = MTG.new(attributes).tap {|card| card.save_standard_down}
53
+ end
54
+
55
+ def self.all(format)
56
+ #iterate through each instance that was appended into class variable during initialization
57
+ format.each_with_index do |card, number|
58
+ puts ""
59
+ puts "|- #{number + 1} -|".fg COLORS[4]
60
+ puts ""
61
+ #line below helps resolve glitch that allows 'ghost/invalid' cards to be selected from Parser.purchase
62
+ if number < Parser.table_length
63
+ #iterate through each instance method that was defined for the stored instance variable
64
+ card.instance_variables.each_with_index do |value, index|
65
+ #returns the value of the instance method applied to the instance
66
+ #with an index value of the first/last, key/value pairs ordered in Parser.scrape_cards
67
+ #associates a named definition of the values by titling it from constant ATTRIBUTES
68
+ if index < 4
69
+ puts "#{ATTRIBUTES[index].fg COLORS[2]} #{card.instance_variable_get(value)}"
70
+ end
71
+ end
72
+ end
73
+ puts ""
74
+ print " ".bg COLORS[7]
75
+ end
76
+ end
77
+
78
+ #hack that resolves glitch that would display duplicate
79
+ #recursions in the selected cards to show by user request in CLI.set_input
80
+ def self.store_temp_array(array)
81
+ @@temp_array = array
82
+ self.all(@@temp_array)
83
+ @@temp_array.clear
84
+ end
85
+
86
+ def self.search_modern_up
87
+ self.all(@@modern_up)
88
+ end
89
+
90
+ def self.search_modern_down
91
+ self.all(@@modern_down)
92
+ end
93
+
94
+ def self.search_standard_up
95
+ self.all(@@standard_up)
96
+ end
97
+
98
+ def self.search_standard_down
99
+ self.all(@@standard_down)
100
+ end
101
+
102
+ def self.modern_up
103
+ @@modern_up
104
+ end
105
+
106
+ def self.modern_down
107
+ @@modern_down
108
+ end
109
+
110
+ def self.standard_up
111
+ @@standard_up
112
+ end
113
+
114
+ def self.standard_down
115
+ @@standard_down
116
+ end
117
+
118
+ end
@@ -0,0 +1,143 @@
1
+ class Parser
2
+ @@overall_card_rows = nil
3
+ @@overall_format_options = []
4
+ @@time_review = []
5
+
6
+ def self.scrape_cards
7
+ self.card_counter
8
+ #checks if the class variable array for the MTG class is empty or not
9
+ if @@overall_format_options[9].call.empty? == false
10
+ #if user has already parsed the same content before, then scrape the locally stored variables instead of the website
11
+ MTG.store_temp_array(@@overall_format_options[9].call)
12
+ else @@overall_format_options[9].call.empty? == true
13
+ #creates a new sql table for gathering the 'to be' scraped content
14
+ @@overall_format_options[5].call
15
+ #exception handling block implemented for possible errors (i.e. website is down)
16
+ retries = 3
17
+ #'retries' will hold the amount of attempts done to block until it quits if a scraping error is not resolved
18
+ begin
19
+ agent = Mechanize.new
20
+ #'agent' will help me to make the website identify what type of user is accessing the content
21
+ #I also want to have the site understand the request that referred me to the page
22
+ agent.pre_connect_hooks << lambda do |agent, request|
23
+ agent.user_agent = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"
24
+ request["Referer"] = "http://www.mtgprice.com/"
25
+ end
26
+ #browser parsing begins in local variable 'doc'
27
+ doc = agent.get "http://www.mtgprice.com/taneLayout/mtg_price_tracker.jsp?period=DAILY"
28
+ rescue StandardError=>e
29
+ puts "Error: #{e}"
30
+ #implementing additonal retry to see if a resolution can be attained by reconnecting to site
31
+ if retries > 0
32
+ puts "Trying #{retries} more times"
33
+ retries -= 1
34
+ sleep 1
35
+ retry
36
+ else
37
+ puts "Unable to resolve #{e}"
38
+ end
39
+ else
40
+ #if no error was attained from 'begin' of block till now, then the card scraping commences
41
+ doc.css(@@overall_format_options[0]).each do |row|
42
+ #parsing is now initialized into MTG class, with key/value pairs for its scraped attributes
43
+ row = self.parser_format(hash = {
44
+ card: row.css(".card a")[0].text,
45
+ sets: row.css(".set a")[0].text,
46
+ market_price: row.css(".value")[0].text.split[0].gsub!("$", "").to_f,
47
+ price_fluctuate: row.css("td:last-child").text,
48
+ image: Nokogiri::HTML(open("http://www.mtgprice.com#{row.css(".card a").attribute("href").value}")).css(".card-img img").attribute("src").value
49
+ # ^^ had to go another level deep to access a better quality image from its full product listing
50
+ })
51
+ #since a stored method in an array can't have a locally passed argument I compromised by just having the class name passed from the class variable array to the method
52
+ #I also make sure that no duplicate information may be transferred into the table by comparing the card row/number count
53
+ @@overall_format_options[6].create(hash) if @@overall_format_options[6].table_rows < self.table_length
54
+ end
55
+ ensure
56
+ sleep(0.3)
57
+ end
58
+ end
59
+ end
60
+
61
+ def self.card_counter
62
+ @@overall_card_rows = nil
63
+ #shows how many rows(amount of cards) there are in total for the page as a constructed array
64
+ rows = Nokogiri::HTML(open("http://www.mtgprice.com/taneLayout/mtg_price_tracker.jsp?period=DAILY")).css(@@overall_format_options[0])[0..-1]
65
+ @@overall_card_rows = "#{rows.length}".to_i
66
+ puts "loading the #{@@overall_format_options[1]} #{@@overall_card_rows} #{@@overall_format_options[2]} #{@@overall_format_options[3]} on the market for today..."; sleep(1);
67
+ print "Please be patient"; print "."; sleep(1); print "."; sleep(1); print "."; sleep(1); print "."; sleep(1);
68
+ puts ""
69
+ puts ""
70
+ puts "-------------------------------------------------"
71
+ puts ""
72
+ puts " ".bg COLORS[7]
73
+ end
74
+
75
+ def self.select_format
76
+ @@overall_format_options.clear
77
+ input = gets.strip.to_i
78
+ case input
79
+ when 1
80
+ #the methods at the end of these arrays are stored references that can be called externally with the .call method #=> http://stackoverflow.com/questions/13948910/ruby-methods-as-array-elements-how-do-they-work
81
+ @@overall_format_options = ["#top50Standard tr", "top", "Standard", "#{"gainers".fg COLORS[4]}", StandardRise.method(:remove_table), StandardRise.method(:create_table), StandardRise, StandardRise.method(:make_csv_file), MTG.method(:search_standard_up), MTG.method(:standard_up)]
82
+ when 2
83
+ @@overall_format_options = ["#top50Modern tr", "top", "Modern", "#{"gainers".fg COLORS[4]}", ModernRise.method(:remove_table), ModernRise.method(:create_table), ModernRise, ModernRise.method(:make_csv_file), MTG.method(:search_modern_up), MTG.method(:modern_up)]
84
+ when 3
85
+ @@overall_format_options = ["#bottom50Standard tr", "bottom", "Standard", "#{"crashers".fg COLORS[6]}", StandardFall.method(:remove_table), StandardFall.method(:create_table), StandardFall, StandardFall.method(:make_csv_file), MTG.method(:search_standard_down), MTG.method(:standard_down)]
86
+ when 4
87
+ @@overall_format_options = ["#bottom50Modern tr", "bottom", "Modern", "#{"crashers".fg COLORS[6]}", ModernFall.method(:remove_table), ModernFall.method(:create_table), ModernFall, ModernFall.method(:make_csv_file), MTG.method(:search_modern_down), MTG.method(:modern_down)]
88
+ else
89
+ CLI.set_input
90
+ end
91
+ end
92
+
93
+ #used within self.scrape_cards, it assists with the assigning of instances to the preferred class variable in MTG
94
+ def self.parser_format(attributes)
95
+ if self.format_name == "StandardRise"
96
+ MTG.create_standard_up(attributes)
97
+ elsif self.format_name == "ModernRise"
98
+ MTG.create_modern_up(attributes)
99
+ elsif self.format_name == "StandardFall"
100
+ MTG.create_standard_down(attributes)
101
+ else self.format_name == "ModernFall"
102
+ MTG.create_modern_down(attributes)
103
+ end
104
+ end
105
+
106
+ def self.display_cards
107
+ @@overall_format_options[8].call
108
+ end
109
+
110
+ def self.format_name
111
+ "#{@@overall_format_options[6]}"
112
+ end
113
+
114
+ def self.table_length
115
+ @@overall_card_rows
116
+ end
117
+
118
+ def self.purchase
119
+ input = gets.strip.to_i
120
+ @@overall_format_options[6].buy_link(input)
121
+ end
122
+
123
+ def self.csv
124
+ #Klass.make_csv_file
125
+ @@overall_format_options[7].call
126
+ puts ""
127
+ puts "The #{"CSV".fg COLORS[3]} file has been saved to your hard disk"
128
+ puts "---------------------------------------------"
129
+ puts ""
130
+ end
131
+
132
+ def self.update_date
133
+ time = Nokogiri::HTML(open("http://www.mtgprice.com/taneLayout/mtg_price_tracker.jsp?period=DAILY"))
134
+ time.css(".span6 h3")[0].text.split.join(" ").gsub!("Updated:", "")
135
+ end
136
+
137
+ # used to clear the tables so that the next re-run will have new, updated content from the website
138
+ def self.reset_query_info
139
+ @@time_review = [StandardRise.method(:remove_table), ModernRise.method(:remove_table), StandardFall.method(:remove_table), ModernFall.method(:remove_table)]
140
+ @@time_review.each_with_index {|method, index| @@time_review[index].call}
141
+ end
142
+
143
+ end
@@ -0,0 +1,25 @@
1
+ require_relative '../concerns/persistable'
2
+ class ModernFall
3
+ include Persistable::InstanceMethods
4
+ extend Persistable::ClassMethods
5
+
6
+ #metaprogramming the hash to convert keys to attr_accessor's and also for inserting the values to the sql strings
7
+ ATTRS = {
8
+ :id => "INTEGER PRIMARY KEY",
9
+ :card => "TEXT",
10
+ :sets => "TEXT",
11
+ :market_price => "INTEGER",
12
+ :price_fluctuate => "TEXT",
13
+ :image => "TEXT"
14
+ }
15
+
16
+ #reader that can be accessed by Persistable module to know the unique class's constant
17
+ def self.attributes
18
+ ATTRS
19
+ end
20
+
21
+ #abstracting the collection of keys into attributes
22
+ self.attributes.keys.each do |key|
23
+ attr_accessor key
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ require_relative '../concerns/persistable'
2
+ class ModernRise
3
+ include Persistable::InstanceMethods
4
+ extend Persistable::ClassMethods
5
+
6
+ #metaprogramming the hash to convert keys to attr_accessor's and also for inserting the values to the sql strings
7
+ ATTRS = {
8
+ :id => "INTEGER PRIMARY KEY",
9
+ :card => "TEXT",
10
+ :sets => "TEXT",
11
+ :market_price => "INTEGER",
12
+ :price_fluctuate => "TEXT",
13
+ :image => "TEXT"
14
+ }
15
+
16
+ #reader that can be accessed by Persistable module to know the unique class's constant
17
+ def self.attributes
18
+ ATTRS
19
+ end
20
+
21
+ #abstracting the collection of keys into attributes
22
+ self.attributes.keys.each do |key|
23
+ attr_accessor key
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ require_relative '../concerns/persistable'
2
+ class StandardFall
3
+ include Persistable::InstanceMethods
4
+ extend Persistable::ClassMethods
5
+
6
+ #metaprogramming the hash to convert keys to attr_accessor's and also for inserting the values to the sql strings
7
+ ATTRS = {
8
+ :id => "INTEGER PRIMARY KEY",
9
+ :card => "TEXT",
10
+ :sets => "TEXT",
11
+ :market_price => "INTEGER",
12
+ :price_fluctuate => "TEXT",
13
+ :image => "TEXT"
14
+ }
15
+
16
+ #reader that can be accessed by Persistable module to know the unique class's constant
17
+ def self.attributes
18
+ ATTRS
19
+ end
20
+
21
+ #abstracting the collection of keys into attributes
22
+ self.attributes.keys.each do |key|
23
+ attr_accessor key
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ require_relative '../concerns/persistable'
2
+ class StandardRise
3
+ include Persistable::InstanceMethods
4
+ extend Persistable::ClassMethods
5
+
6
+ #metaprogramming the hash to convert keys to attr_accessor's and also for inserting the values to the sql strings
7
+ ATTRS = {
8
+ :id => "INTEGER PRIMARY KEY",
9
+ :card => "TEXT",
10
+ :sets => "TEXT",
11
+ :market_price => "INTEGER",
12
+ :price_fluctuate => "TEXT",
13
+ :image => "TEXT"
14
+ }
15
+
16
+ #reader that can be accessed by Persistable module to know the unique class's constant
17
+ def self.attributes
18
+ ATTRS
19
+ end
20
+
21
+ #abstracting the collection of keys into attributes
22
+ self.attributes.keys.each do |key|
23
+ attr_accessor key
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ module MTGCardFinder
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,42 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'mtg_card_finder/version.rb'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "mtg-card-finder"
8
+ spec.version = MTGCardFinder::VERSION
9
+ spec.authors = ["Juan Gongora"]
10
+ spec.email = ["gongora.animations@gmail.com"]
11
+
12
+ spec.summary = %q{Daily market analyzer for MTG cards}
13
+ spec.description = %q{Find the highest rising/falling card prices on the MTG open market}
14
+ spec.homepage = "https://github.com/JuanGongora/mtg-card-finder"
15
+ spec.license = "MIT"
16
+
17
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
19
+ # if spec.respond_to?(:metadata)
20
+ # spec.metadata['allowed_push_host'] = "Set to 'http://mygemserver.com'"
21
+ # else
22
+ # raise "RubyGems 2.0 or newer is required to protect against " \
23
+ # "public gem pushes."
24
+ # end
25
+
26
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
27
+ f.match(%r{^(test|spec|features)/})
28
+ end
29
+ spec.bindir = "exe"
30
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
+ spec.require_paths = ["lib"]
32
+
33
+ spec.add_development_dependency "bundler", "~> 1.14"
34
+ spec.add_development_dependency "rake", "~> 12.0"
35
+ spec.add_development_dependency "pry", "~> 0.10.4"
36
+ spec.add_dependency "rubysl-open-uri", "~> 2.0"
37
+ spec.add_dependency "nokogiri", "~> 1.7.1"
38
+ spec.add_dependency "tco", "~> 0.1.8"
39
+ spec.add_dependency "sqlite3", "~> 1.3.13"
40
+ spec.add_dependency "mechanize", "~> 2.7.5"
41
+ spec.add_dependency "require_all", "~> 1.4"
42
+ end
data/spec.md ADDED
@@ -0,0 +1,7 @@
1
+ # Specifications for the CLI Assessment
2
+
3
+ Specs:
4
+ - [x] Have a CLI for interfacing with the application
5
+ - [ ] Pull data from an external source
6
+ - [ ] Implement both list and detail views
7
+
metadata ADDED
@@ -0,0 +1,195 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mtg-card-finder
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Juan Gongora
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-04-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.14'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.14'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '12.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '12.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.10.4
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.10.4
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubysl-open-uri
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: nokogiri
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 1.7.1
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 1.7.1
83
+ - !ruby/object:Gem::Dependency
84
+ name: tco
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.1.8
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.1.8
97
+ - !ruby/object:Gem::Dependency
98
+ name: sqlite3
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 1.3.13
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 1.3.13
111
+ - !ruby/object:Gem::Dependency
112
+ name: mechanize
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 2.7.5
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 2.7.5
125
+ - !ruby/object:Gem::Dependency
126
+ name: require_all
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '1.4'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '1.4'
139
+ description: Find the highest rising/falling card prices on the MTG open market
140
+ email:
141
+ - gongora.animations@gmail.com
142
+ executables: []
143
+ extensions: []
144
+ extra_rdoc_files: []
145
+ files:
146
+ - ".gitignore"
147
+ - CODE_OF_CONDUCT.md
148
+ - Gemfile
149
+ - Gemfile.lock
150
+ - LICENSE.txt
151
+ - README.md
152
+ - Rakefile
153
+ - bin/console
154
+ - bin/mtg-card-finder
155
+ - bin/setup
156
+ - config/environment.rb
157
+ - db/cards.db
158
+ - lib/mtg_card_finder.rb
159
+ - lib/mtg_card_finder/cli.rb
160
+ - lib/mtg_card_finder/color.rb
161
+ - lib/mtg_card_finder/concerns/persistable.rb
162
+ - lib/mtg_card_finder/mtg.rb
163
+ - lib/mtg_card_finder/parser.rb
164
+ - lib/mtg_card_finder/tables/modern_fall.rb
165
+ - lib/mtg_card_finder/tables/modern_rise.rb
166
+ - lib/mtg_card_finder/tables/standard_fall.rb
167
+ - lib/mtg_card_finder/tables/standard_rise.rb
168
+ - lib/mtg_card_finder/version.rb
169
+ - mtg-card-finder.gemspec
170
+ - spec.md
171
+ homepage: https://github.com/JuanGongora/mtg-card-finder
172
+ licenses:
173
+ - MIT
174
+ metadata: {}
175
+ post_install_message:
176
+ rdoc_options: []
177
+ require_paths:
178
+ - lib
179
+ required_ruby_version: !ruby/object:Gem::Requirement
180
+ requirements:
181
+ - - ">="
182
+ - !ruby/object:Gem::Version
183
+ version: '0'
184
+ required_rubygems_version: !ruby/object:Gem::Requirement
185
+ requirements:
186
+ - - ">="
187
+ - !ruby/object:Gem::Version
188
+ version: '0'
189
+ requirements: []
190
+ rubyforge_project:
191
+ rubygems_version: 2.6.11
192
+ signing_key:
193
+ specification_version: 4
194
+ summary: Daily market analyzer for MTG cards
195
+ test_files: []