football_now 0.1.4
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 +10 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +46 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/football_now +5 -0
- data/bin/setup +7 -0
- data/config/environment.rb +23 -0
- data/data/.gitignore +2 -0
- data/exe/football_now +5 -0
- data/football_now.gemspec +38 -0
- data/lib/cli.rb +170 -0
- data/lib/db.rb +67 -0
- data/lib/football_now.rb +3 -0
- data/lib/football_now/version.rb +14 -0
- data/lib/importer.rb +41 -0
- data/lib/league.rb +62 -0
- data/lib/match.rb +47 -0
- data/lib/scraper.rb +86 -0
- data/lib/team.rb +45 -0
- metadata +171 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 7dada34844e1d8c67413b9b4c018b9e86bc9bd74
|
|
4
|
+
data.tar.gz: f2f629b4a491c40df0991bf3ac0b7bca2cb6d508
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 62aa6b58d2634d527e9a06b5904eca84c79d70e38b5251da66af9f9c43e7cc6369390b0883267141fb074fab0ba8e634b86925f0b9ac81c4ea82acc0ed681ce0
|
|
7
|
+
data.tar.gz: 6870df52f01351e5e393850d076f3ab033b2111f60b32258cab588604f5275326175564526b6d2c1b0c62b5309dff4bb804a101d2b0a707c7faf0cc20e614df3
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2016 gnfisher
|
|
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,46 @@
|
|
|
1
|
+
# FootballNow
|
|
2
|
+
|
|
3
|
+
Football Now brings you all the information you need from top Football leagues in Europe.
|
|
4
|
+
|
|
5
|
+
## Project goals
|
|
6
|
+
|
|
7
|
+
- User can get latest scores (including live scores if match is ongoing)
|
|
8
|
+
- User can get league standings
|
|
9
|
+
- User can get list of results for a specific team
|
|
10
|
+
- User can get list of upcoming matches for a specific team
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
Add this line to your application's Gemfile:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
gem 'football_now'
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
And then execute:
|
|
22
|
+
|
|
23
|
+
$ bundle
|
|
24
|
+
|
|
25
|
+
Or install it yourself as:
|
|
26
|
+
|
|
27
|
+
$ gem install football_now
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
TODO: Write usage instructions here
|
|
32
|
+
|
|
33
|
+
## Development
|
|
34
|
+
|
|
35
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
36
|
+
|
|
37
|
+
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).
|
|
38
|
+
|
|
39
|
+
## Contributing
|
|
40
|
+
|
|
41
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/football_now.
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
## License
|
|
45
|
+
|
|
46
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "football_now"
|
|
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
|
data/bin/football_now
ADDED
data/bin/setup
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# require 'require_all'
|
|
2
|
+
require 'open-uri'
|
|
3
|
+
require 'nokogiri'
|
|
4
|
+
require 'capybara'
|
|
5
|
+
require 'capybara/dsl'
|
|
6
|
+
require 'capybara/poltergeist'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
Capybara.default_driver = :poltergeist
|
|
10
|
+
Capybara.run_server = false
|
|
11
|
+
Capybara.default_max_wait_time = 120
|
|
12
|
+
Capybara.register_driver :poltergeist do |app|
|
|
13
|
+
Capybara::Poltergeist::Driver.new(app, js_errors: false, phantomjs_options: ['--load-images=false', '--disk-cache=false'])
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
require 'football_now/version'
|
|
17
|
+
require 'cli'
|
|
18
|
+
require 'db'
|
|
19
|
+
require 'importer'
|
|
20
|
+
require 'league'
|
|
21
|
+
require 'match'
|
|
22
|
+
require 'scraper'
|
|
23
|
+
require 'team'
|
data/data/.gitignore
ADDED
data/exe/football_now
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'football_now/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "football_now"
|
|
8
|
+
spec.version = FootballNow::VERSION
|
|
9
|
+
spec.authors = ["gnfisher"]
|
|
10
|
+
spec.email = ["me@gnfisher.com"]
|
|
11
|
+
|
|
12
|
+
spec.summary = %q{Get standings, results and stats on the top four European leagues from your command line.}
|
|
13
|
+
spec.description = %q{Scraping a popular football site, this gem allows you to navigate the standings, results, and stats of the teams in Europe's top competitions.}
|
|
14
|
+
spec.homepage = "http://gnfisher.com"
|
|
15
|
+
spec.license = "MIT"
|
|
16
|
+
|
|
17
|
+
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
|
|
18
|
+
# delete this section to allow pushing this gem to any host.
|
|
19
|
+
if spec.respond_to?(:metadata)
|
|
20
|
+
spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
|
21
|
+
else
|
|
22
|
+
raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
26
|
+
spec.bindir = "exe"
|
|
27
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
28
|
+
spec.require_paths = ["lib", "config", "data"]
|
|
29
|
+
|
|
30
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
|
31
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
|
32
|
+
spec.add_development_dependency "rspec"
|
|
33
|
+
|
|
34
|
+
spec.add_runtime_dependency "nokogiri"
|
|
35
|
+
spec.add_runtime_dependency "require_all"
|
|
36
|
+
spec.add_runtime_dependency "capybara"
|
|
37
|
+
spec.add_runtime_dependency "poltergeist"
|
|
38
|
+
end
|
data/lib/cli.rb
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
class FootballNow::CLI
|
|
2
|
+
|
|
3
|
+
def call
|
|
4
|
+
welcome_screen
|
|
5
|
+
list_leagues
|
|
6
|
+
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def list_leagues
|
|
10
|
+
puts "\n\n"
|
|
11
|
+
puts "Available Leagues:"
|
|
12
|
+
puts "=================="
|
|
13
|
+
FootballNow::League.print_leagues
|
|
14
|
+
puts ""
|
|
15
|
+
puts "You can:"
|
|
16
|
+
puts " - type `<#> recent results` for scores"
|
|
17
|
+
puts " - type `<#> get standings` for the league table"
|
|
18
|
+
puts " - type `<#> list teams` to explore a team indepth"
|
|
19
|
+
puts " - type `exit` to quit now."
|
|
20
|
+
puts ""
|
|
21
|
+
puts "Usage: typing `1 list teams` will list all teams in the first league listed."
|
|
22
|
+
puts ""
|
|
23
|
+
puts "What would you like to do?"
|
|
24
|
+
|
|
25
|
+
input = get_user_input
|
|
26
|
+
league = FootballNow::League.get_league_by_index(input.split("").first.to_i - 1) if input =~ /^\d/
|
|
27
|
+
|
|
28
|
+
case input
|
|
29
|
+
when "exit"
|
|
30
|
+
goodbye_message
|
|
31
|
+
when /^\D/
|
|
32
|
+
puts "Sorry, I didn't understand..."
|
|
33
|
+
list_leagues
|
|
34
|
+
when /recent results/
|
|
35
|
+
list_recent_results(league)
|
|
36
|
+
list_leagues
|
|
37
|
+
when /get standings/
|
|
38
|
+
get_standings(league)
|
|
39
|
+
when /list teams/
|
|
40
|
+
list_teams(league)
|
|
41
|
+
else
|
|
42
|
+
puts "Sorry, I didn't understand..."
|
|
43
|
+
list_leagues
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def list_recent_results(league)
|
|
48
|
+
puts ""
|
|
49
|
+
puts "#{league.name.upcase}"
|
|
50
|
+
puts "Round #{league.current_round}:"
|
|
51
|
+
puts ""
|
|
52
|
+
|
|
53
|
+
FootballNow::Match.get_recent_results(league).each do |result|
|
|
54
|
+
puts " #{result.home_score} #{result.home_team.name}"
|
|
55
|
+
puts " #{result.away_score} #{result.away_team.name}"
|
|
56
|
+
puts ""
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
puts "Hit enter to go back or exit to quit."
|
|
60
|
+
goodbye_message if get_user_input.downcase == "exit"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def get_standings(league)
|
|
64
|
+
table = league.get_standings
|
|
65
|
+
|
|
66
|
+
puts ""
|
|
67
|
+
puts "#{league.name.upcase} : Up to Round #{league.current_round}"
|
|
68
|
+
puts "=========================================="
|
|
69
|
+
print_table(table)
|
|
70
|
+
puts ""
|
|
71
|
+
puts "Hit enter to return to league list or exit to quit."
|
|
72
|
+
|
|
73
|
+
get_user_input.downcase == "exit" ? goodbye_message : list_leagues
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def list_teams(league)
|
|
77
|
+
puts ""
|
|
78
|
+
puts "#{league.name.upcase} : Up to Round #{league.current_round}"
|
|
79
|
+
puts ""
|
|
80
|
+
|
|
81
|
+
format = '%-4s %-20s'
|
|
82
|
+
league.teams.sort_by{|team| team.name}.each_with_index do |team, index|
|
|
83
|
+
puts format % [index + 1, team.name]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
puts ""
|
|
87
|
+
puts "You can:"
|
|
88
|
+
puts " - type `<#> results` for all results"
|
|
89
|
+
puts " - type `<#> stats` for all avaulable stats"
|
|
90
|
+
puts " - type `back` to return to the first menu"
|
|
91
|
+
puts " - type `exit` to quit now."
|
|
92
|
+
puts ""
|
|
93
|
+
puts "Usage: typing `1 results` will list all results for that team."
|
|
94
|
+
puts ""
|
|
95
|
+
puts "What would you like to do?"
|
|
96
|
+
|
|
97
|
+
input = get_user_input
|
|
98
|
+
team = league.teams.sort_by{|team| team.name}[input.to_i - 1] if input =~ /^\d/
|
|
99
|
+
|
|
100
|
+
case input
|
|
101
|
+
when /results/
|
|
102
|
+
list_all_results(team)
|
|
103
|
+
list_teams(league)
|
|
104
|
+
when /stats/
|
|
105
|
+
list_stats(team)
|
|
106
|
+
list_teams(league)
|
|
107
|
+
when /back/
|
|
108
|
+
list_leagues
|
|
109
|
+
when /exit/
|
|
110
|
+
goodbye_message
|
|
111
|
+
else
|
|
112
|
+
puts "Sorry, I didn't understand you."
|
|
113
|
+
list_teams
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def list_all_results(team)
|
|
118
|
+
puts ""
|
|
119
|
+
puts "#{team.name.upcase}: League Results"
|
|
120
|
+
puts ""
|
|
121
|
+
team.matches.each do |match|
|
|
122
|
+
puts "Round #{match.round}:"
|
|
123
|
+
puts " #{match.home_score} #{match.home_team.name}"
|
|
124
|
+
puts " #{match.away_score} #{match.away_team.name}"
|
|
125
|
+
puts ""
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
puts "Hit enter to go back or exit to quit."
|
|
129
|
+
goodbye_message if get_user_input.downcase == "exit"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def list_stats(team)
|
|
133
|
+
puts ""
|
|
134
|
+
puts "#{team.name.upcase}: League Stats"
|
|
135
|
+
puts ""
|
|
136
|
+
format = '%-5s %-5s %-8s %-10s %-10s %-13s %s'
|
|
137
|
+
puts format % ['Wins', 'Draws', 'Losses', 'Points', 'Goals For', 'Goals Against', 'Matches Played']
|
|
138
|
+
puts format % [ team.wins, team.draws, team.losses, team.points, team.goals_for, team.goals_against, team.matches.size ]
|
|
139
|
+
puts "Hit enter to go back or exit to quit."
|
|
140
|
+
goodbye_message if get_user_input.downcase == "exit"
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def print_table(table)
|
|
144
|
+
format = '%-4s %-20s %-3s %-3s %-3s %s'
|
|
145
|
+
puts format % ['#', 'Team', 'W', 'D', 'L', 'Pts']
|
|
146
|
+
table.each do |team|
|
|
147
|
+
puts format % [ team.standing, team.name, team.wins, team.draws, team.losses, team.points]
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def welcome_screen
|
|
152
|
+
puts "Welcome to Football Now"
|
|
153
|
+
load_all_data
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def load_all_data
|
|
157
|
+
puts "Loading data from www.soccer24.com..."
|
|
158
|
+
puts "This may take a minute or two..."
|
|
159
|
+
FootballNow::Importer.generate
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def goodbye_message
|
|
163
|
+
puts "Until next time!"
|
|
164
|
+
exit 0
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def get_user_input
|
|
168
|
+
gets.strip
|
|
169
|
+
end
|
|
170
|
+
end
|
data/lib/db.rb
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
class FootballNow::DB
|
|
2
|
+
|
|
3
|
+
extend Capybara::DSL
|
|
4
|
+
|
|
5
|
+
def self.get_html(url)
|
|
6
|
+
url_array = strip_url(url)
|
|
7
|
+
file_path = file_path_from(url_array)
|
|
8
|
+
|
|
9
|
+
if File.exists?(file_path) && not_expired?(file_path)
|
|
10
|
+
File.read(file_path)
|
|
11
|
+
else
|
|
12
|
+
return_html(url_array, url)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.get_leagues_html(url)
|
|
17
|
+
page_html = open(url).read
|
|
18
|
+
url_array = strip_url(url)
|
|
19
|
+
save_as_file(url_array, page_html)
|
|
20
|
+
page_html
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.get_standings_html(url)
|
|
24
|
+
visit(url)
|
|
25
|
+
sleep(1)
|
|
26
|
+
|
|
27
|
+
url_array = strip_url(url)
|
|
28
|
+
save_as_file(url_array, page.html)
|
|
29
|
+
page.html
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.get_results_html(url)
|
|
33
|
+
visit(url)
|
|
34
|
+
sleep(1)
|
|
35
|
+
click_link("Show more matches")
|
|
36
|
+
sleep(2)
|
|
37
|
+
click_link("Show more matches")
|
|
38
|
+
sleep(2)
|
|
39
|
+
|
|
40
|
+
url_array = strip_url(url)
|
|
41
|
+
save_as_file(url_array, page.html)
|
|
42
|
+
page.html
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.file_path_from(url_array)
|
|
46
|
+
url_array[1] == nil ? "#{FootballNow::DB_PATH}/leagues.html" :
|
|
47
|
+
"#{FootballNow::DB_PATH}/#{url_array[2]}-#{url_array[3]}.html"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.not_expired?(file_path)
|
|
51
|
+
Time.now - File.mtime(file_path) < FootballNow::TIME_OUT
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.strip_url(url)
|
|
55
|
+
url.gsub("http://", "").split("/")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.save_as_file(url_array, html)
|
|
59
|
+
leagues_file = open(file_path_from(url_array), "w")
|
|
60
|
+
leagues_file.write(html)
|
|
61
|
+
leagues_file.close
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def self.return_html(url_array, url)
|
|
65
|
+
url_array[1] == nil ? get_leagues_html(url) : send("get_#{url_array[3]}_html", url)
|
|
66
|
+
end
|
|
67
|
+
end
|
data/lib/football_now.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module FootballNow
|
|
2
|
+
VERSION = "0.1.4"
|
|
3
|
+
|
|
4
|
+
# Football Now Config options
|
|
5
|
+
#
|
|
6
|
+
# DB_PATH
|
|
7
|
+
# Folder in the directory where scraped HTML pages are saved.
|
|
8
|
+
DB_PATH = "#{File.expand_path("../..", __dir__)}/data"
|
|
9
|
+
|
|
10
|
+
# TIME_OUT
|
|
11
|
+
# The number of seconds that a saved copy of the HTML file is valid for.
|
|
12
|
+
# Default = 24 hours
|
|
13
|
+
TIME_OUT = 86400
|
|
14
|
+
end
|
data/lib/importer.rb
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
class FootballNow::Importer
|
|
2
|
+
|
|
3
|
+
def self.generate
|
|
4
|
+
create_leagues
|
|
5
|
+
puts "#{FootballNow::League.all.count} Leagues loaded..."
|
|
6
|
+
create_teams
|
|
7
|
+
puts "#{FootballNow::Team.all.count} Teams loaded..."
|
|
8
|
+
create_matches
|
|
9
|
+
puts "#{FootballNow::Match.all.count} Matches loaded..."
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.create_leagues
|
|
13
|
+
FootballNow::Scraper.scrape_leagues.each do |league|
|
|
14
|
+
FootballNow::League.create_from_hash(league)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.create_teams
|
|
19
|
+
FootballNow::League.all.each do |league|
|
|
20
|
+
teams_hash(league.league_url).each do |team_hash|
|
|
21
|
+
FootballNow::Team.create_from_hash(team_hash)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.teams_hash(league_url)
|
|
27
|
+
FootballNow::Scraper.scrape_teams(league_url)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.create_matches
|
|
31
|
+
FootballNow::League.all.each do |league|
|
|
32
|
+
matches_hash(league.league_url).each do |match_hash|
|
|
33
|
+
FootballNow::Match.create_from_hash(match_hash)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.matches_hash(league_url)
|
|
39
|
+
FootballNow::Scraper.scrape_matches(league_url)
|
|
40
|
+
end
|
|
41
|
+
end
|
data/lib/league.rb
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
class FootballNow::League
|
|
2
|
+
|
|
3
|
+
attr_accessor :name, :league_url, :teams
|
|
4
|
+
|
|
5
|
+
@@all = []
|
|
6
|
+
|
|
7
|
+
def initialize(name, league_url)
|
|
8
|
+
@name = name
|
|
9
|
+
@league_url = league_url
|
|
10
|
+
@teams = []
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def save
|
|
14
|
+
@@all << self
|
|
15
|
+
self
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def add_team(team)
|
|
19
|
+
team.league ||= self
|
|
20
|
+
@teams << team unless @teams.include? team
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def matches
|
|
24
|
+
@teams.map { |team| team.matches }.flatten.uniq
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def get_standings
|
|
28
|
+
@teams.sort {|a, b| a.standing.to_i <=> b.standing.to_i }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def current_round
|
|
32
|
+
FootballNow::Match.most_recent_round_number(self)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.find_by_name(name)
|
|
36
|
+
self.all.detect {|league| league.name.downcase == name.downcase}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.new_from_hash(league_hash)
|
|
40
|
+
new(league_hash[:name], league_hash[:league_url])
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.create_from_hash(league_hash)
|
|
44
|
+
new_from_hash(league_hash).tap(&:save)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.all
|
|
48
|
+
@@all
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.print_leagues
|
|
52
|
+
self.all.each.with_index(1) {|league, index| puts "#{index}. #{league.name}"}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.get_league_by_index(index)
|
|
56
|
+
self.all[index]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.reset
|
|
60
|
+
@@all.clear
|
|
61
|
+
end
|
|
62
|
+
end
|
data/lib/match.rb
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
class FootballNow::Match
|
|
2
|
+
|
|
3
|
+
attr_accessor :round, :date, :home_score, :away_score
|
|
4
|
+
attr_reader :home_team, :away_team
|
|
5
|
+
|
|
6
|
+
@@all = []
|
|
7
|
+
|
|
8
|
+
def initialize(opt = {})
|
|
9
|
+
opt.each {|method, arg| send("#{method}=", arg) if respond_to?("#{method}=") }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def home_team=(team)
|
|
13
|
+
@home_team = team
|
|
14
|
+
team.add_match(self, 'home_team')
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def away_team=(team)
|
|
18
|
+
@away_team = team
|
|
19
|
+
team.add_match(self, 'away_team')
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def league
|
|
23
|
+
@home_team.league
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.create_from_hash(match_hash)
|
|
27
|
+
new(match_hash).tap(&:save)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def save
|
|
31
|
+
@@all << self
|
|
32
|
+
self
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.all
|
|
36
|
+
@@all
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.get_recent_results(league)
|
|
40
|
+
round_number = most_recent_round_number(league)
|
|
41
|
+
league.matches.select {|match| match.round == round_number}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.most_recent_round_number(league)
|
|
45
|
+
league.matches.max {|a,b| a.round <=> b.round}.round
|
|
46
|
+
end
|
|
47
|
+
end
|
data/lib/scraper.rb
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
class FootballNow::Scraper
|
|
2
|
+
|
|
3
|
+
extend Capybara::DSL
|
|
4
|
+
|
|
5
|
+
BASE_URL = "http://www.soccer24.com"
|
|
6
|
+
LEAGUES = [
|
|
7
|
+
"Premier League", "Primera Division",
|
|
8
|
+
"Bundesliga", "Serie A"
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
def self.scrape_leagues
|
|
12
|
+
doc = FootballNow::DB.get_html(BASE_URL)
|
|
13
|
+
league_list = Nokogiri::HTML(doc).css('.left-menu').first.css('ul li')
|
|
14
|
+
|
|
15
|
+
league_list.map do |row|
|
|
16
|
+
href = row.css('a').attribute('href').value
|
|
17
|
+
league = row.css('a').text
|
|
18
|
+
team_hash = {name: league, league_url: "#{BASE_URL}#{href}"}
|
|
19
|
+
|
|
20
|
+
LEAGUES.include?(league) ? team_hash : nil
|
|
21
|
+
end.compact
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.scrape_teams(league_url)
|
|
25
|
+
doc = FootballNow::DB.get_html(get_standings_page_url(league_url))
|
|
26
|
+
standings_page = Nokogiri::HTML(doc)
|
|
27
|
+
standings = standings_page.css('table#table-type-1 tbody tr')
|
|
28
|
+
league = standings_page.css('.tournament-name').text
|
|
29
|
+
|
|
30
|
+
standings.map do |row|
|
|
31
|
+
goals_for_against = row.css('.goals').first.text.split(':')
|
|
32
|
+
team_hash = {
|
|
33
|
+
name: row.css('.participant_name .team_name_span').text,
|
|
34
|
+
league: FootballNow::League.find_by_name(league),
|
|
35
|
+
wins: row.css('.wins').text,
|
|
36
|
+
draws: row.css('.draws').text,
|
|
37
|
+
losses: row.css('.losses').text,
|
|
38
|
+
standing: row.css('.rank').text.chomp('.'),
|
|
39
|
+
goals_for: goals_for_against[0],
|
|
40
|
+
goals_against: goals_for_against[1],
|
|
41
|
+
points: row.css('.goals')[1].text
|
|
42
|
+
}
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.scrape_matches(league_url)
|
|
47
|
+
doc = FootballNow::DB.get_html(get_matches_page_url(league_url))
|
|
48
|
+
matches_page = Nokogiri::HTML(doc)
|
|
49
|
+
rows = matches_page.css('tbody tr')
|
|
50
|
+
|
|
51
|
+
rows.map do |row|
|
|
52
|
+
if row.css('td').first.text[/Round/]
|
|
53
|
+
@@round = row.css('td').first.text.gsub(/Round /, "").to_i
|
|
54
|
+
nil
|
|
55
|
+
elsif row.css('td.time').text.empty?
|
|
56
|
+
nil
|
|
57
|
+
else
|
|
58
|
+
score = row.css('td.score').text.split(':')
|
|
59
|
+
home_team = row.css('td.team-home').text.gsub(/[[:space:]]/, ' ').strip
|
|
60
|
+
away_team = row.css('td.team-away').text.gsub(/[[:space:]]/, ' ').strip
|
|
61
|
+
match_hash = {
|
|
62
|
+
round: @@round,
|
|
63
|
+
date: row.css('td.time').text.strip,
|
|
64
|
+
home_team: FootballNow::Team.find_by_name(home_team),
|
|
65
|
+
away_team: FootballNow::Team.find_by_name(away_team),
|
|
66
|
+
home_score: score[0].gsub(/[[:space:]]/, ' ').strip,
|
|
67
|
+
away_score: score[1].gsub(/[[:space:]]/, ' ').strip
|
|
68
|
+
}
|
|
69
|
+
end
|
|
70
|
+
end.compact
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def self.get_standings_page_url(league_url)
|
|
76
|
+
league_page = Nokogiri::HTML(open(league_url))
|
|
77
|
+
href = league_page.css('.page-tabs .ifmenu li a:contains("Standings")').attribute('href').value
|
|
78
|
+
"#{BASE_URL}#{href}"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def self.get_matches_page_url(league_url)
|
|
82
|
+
matches_page = Nokogiri::HTML(open(league_url))
|
|
83
|
+
href = matches_page.css('.page-tabs .ifmenu li a:contains("Results")').attribute('href').value
|
|
84
|
+
"#{BASE_URL}#{href}"
|
|
85
|
+
end
|
|
86
|
+
end
|
data/lib/team.rb
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
class FootballNow::Team
|
|
2
|
+
|
|
3
|
+
attr_accessor :name, :league, :wins, :losses, :draws, :goals_for,
|
|
4
|
+
:goals_against, :points, :standing
|
|
5
|
+
attr_reader :matches
|
|
6
|
+
|
|
7
|
+
@@all = []
|
|
8
|
+
|
|
9
|
+
def initialize(name, opt={})
|
|
10
|
+
@name = name
|
|
11
|
+
@matches = []
|
|
12
|
+
opt.each {|method, arg| send("#{method}=", arg) if self.respond_to?("#{method}=")}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def save
|
|
16
|
+
@@all << self
|
|
17
|
+
self
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def league=(league)
|
|
21
|
+
@league = league
|
|
22
|
+
league.add_team(self)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def add_match(match, key)
|
|
26
|
+
match.send("#{key}=", self) unless match.send(key)
|
|
27
|
+
@matches << match
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.find_by_name(team_name)
|
|
31
|
+
@@all.detect {|team| team.name.downcase == team_name.downcase}
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.create_from_hash(team_data)
|
|
35
|
+
team = new(team_data[:name], team_data).tap(&:save)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.all
|
|
39
|
+
@@all
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.reset
|
|
43
|
+
@@all.clear
|
|
44
|
+
end
|
|
45
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: football_now
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.4
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- gnfisher
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2016-03-29 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.10'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.10'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '10.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '10.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rspec
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: nokogiri
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: require_all
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0'
|
|
76
|
+
type: :runtime
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: capybara
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0'
|
|
90
|
+
type: :runtime
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">="
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: poltergeist
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
type: :runtime
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
111
|
+
description: Scraping a popular football site, this gem allows you to navigate the
|
|
112
|
+
standings, results, and stats of the teams in Europe's top competitions.
|
|
113
|
+
email:
|
|
114
|
+
- me@gnfisher.com
|
|
115
|
+
executables:
|
|
116
|
+
- football_now
|
|
117
|
+
extensions: []
|
|
118
|
+
extra_rdoc_files: []
|
|
119
|
+
files:
|
|
120
|
+
- ".gitignore"
|
|
121
|
+
- ".rspec"
|
|
122
|
+
- ".travis.yml"
|
|
123
|
+
- Gemfile
|
|
124
|
+
- LICENSE.txt
|
|
125
|
+
- README.md
|
|
126
|
+
- Rakefile
|
|
127
|
+
- bin/console
|
|
128
|
+
- bin/football_now
|
|
129
|
+
- bin/setup
|
|
130
|
+
- config/environment.rb
|
|
131
|
+
- data/.gitignore
|
|
132
|
+
- exe/football_now
|
|
133
|
+
- football_now.gemspec
|
|
134
|
+
- lib/cli.rb
|
|
135
|
+
- lib/db.rb
|
|
136
|
+
- lib/football_now.rb
|
|
137
|
+
- lib/football_now/version.rb
|
|
138
|
+
- lib/importer.rb
|
|
139
|
+
- lib/league.rb
|
|
140
|
+
- lib/match.rb
|
|
141
|
+
- lib/scraper.rb
|
|
142
|
+
- lib/team.rb
|
|
143
|
+
homepage: http://gnfisher.com
|
|
144
|
+
licenses:
|
|
145
|
+
- MIT
|
|
146
|
+
metadata:
|
|
147
|
+
allowed_push_host: https://rubygems.org
|
|
148
|
+
post_install_message:
|
|
149
|
+
rdoc_options: []
|
|
150
|
+
require_paths:
|
|
151
|
+
- lib
|
|
152
|
+
- config
|
|
153
|
+
- data
|
|
154
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
155
|
+
requirements:
|
|
156
|
+
- - ">="
|
|
157
|
+
- !ruby/object:Gem::Version
|
|
158
|
+
version: '0'
|
|
159
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
160
|
+
requirements:
|
|
161
|
+
- - ">="
|
|
162
|
+
- !ruby/object:Gem::Version
|
|
163
|
+
version: '0'
|
|
164
|
+
requirements: []
|
|
165
|
+
rubyforge_project:
|
|
166
|
+
rubygems_version: 2.4.8
|
|
167
|
+
signing_key:
|
|
168
|
+
specification_version: 4
|
|
169
|
+
summary: Get standings, results and stats on the top four European leagues from your
|
|
170
|
+
command line.
|
|
171
|
+
test_files: []
|