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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +69 -0
- data/Rakefile +14 -0
- data/bin/console +14 -0
- data/bin/mtg-card-finder +5 -0
- data/bin/setup +8 -0
- data/config/environment.rb +10 -0
- data/db/cards.db +0 -0
- data/lib/mtg_card_finder.rb +4 -0
- data/lib/mtg_card_finder/cli.rb +77 -0
- data/lib/mtg_card_finder/color.rb +49 -0
- data/lib/mtg_card_finder/concerns/persistable.rb +213 -0
- data/lib/mtg_card_finder/mtg.rb +118 -0
- data/lib/mtg_card_finder/parser.rb +143 -0
- data/lib/mtg_card_finder/tables/modern_fall.rb +25 -0
- data/lib/mtg_card_finder/tables/modern_rise.rb +25 -0
- data/lib/mtg_card_finder/tables/standard_fall.rb +25 -0
- data/lib/mtg_card_finder/tables/standard_rise.rb +25 -0
- data/lib/mtg_card_finder/version.rb +3 -0
- data/mtg-card-finder.gemspec +42 -0
- data/spec.md +7 -0
- metadata +195 -0
checksums.yaml
ADDED
|
@@ -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
|
data/.gitignore
ADDED
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -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
data/LICENSE.txt
ADDED
|
@@ -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.
|
data/README.md
ADDED
|
@@ -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).
|
data/Rakefile
ADDED
|
@@ -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
|
data/bin/console
ADDED
|
@@ -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__)
|
data/bin/mtg-card-finder
ADDED
data/bin/setup
ADDED
|
@@ -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
|
data/db/cards.db
ADDED
|
File without changes
|
|
@@ -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,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
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: []
|