footballfiltertool 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 302154a71776df58b8e1efb6e16988828d55f55decd2f99cbe867625b83dcebe
4
+ data.tar.gz: 9f5edf3dc8cbf48fe067744ae8b298aec9f5c23425d97b37fb917e2c585b6a19
5
+ SHA512:
6
+ metadata.gz: cce12c7ffa14e0b7840caa1902c4fa3cfaa453329cae77cd6019a04ed5fd9fc1c2faba28a3ed4b95aa217ee37923b66142e092c9e614fdf4dc099adcc3b0783c
7
+ data.tar.gz: fba85d3055e54330ca8c7ee502b70cc277cfdc86fea48bf2b7d2e15b4987a76154d3f41bdf493a341a86e9c14eae99a43f3d9e29ed9d509e29e9391a2ddae901
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "footballfiltertool"
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
+ puts "HELLO WORLD"
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "../lib/footballfiltertool.rb"
4
+ RunCommandLineInteface.run
@@ -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,311 @@
1
+ require_relative '../lib/player.rb'
2
+ require 'nokogiri'
3
+ require 'pry'
4
+ class CommandLineInterface
5
+ attr_accessor :user, :stats_list, :filter_list, :sort_stat, :sort_value, :player_data
6
+
7
+ def initialize(user)
8
+ @user = user
9
+ end
10
+
11
+
12
+ def self.run
13
+ user_cli = CommandLineInterface.new("ADMIN")
14
+ user_cli.start
15
+ end
16
+
17
+ def start
18
+ puts "Welcome to Football Stats Filter Tool"
19
+ puts "Would you like to load some football stats (y/n)"
20
+ input = gets.chomp
21
+ while(input == "y")
22
+ request_year
23
+ print_allowable_attributes
24
+ loop do
25
+ run_query
26
+ puts "Would you like to query another list from this year? (y/n)"
27
+ input = gets.chomp
28
+ if(input != "y")
29
+ break
30
+ end
31
+ @player_data = Player.all
32
+
33
+ end
34
+ puts "Would you like to load another year? (y/n)"
35
+ input = gets.chomp
36
+
37
+ end
38
+ puts "Thank you for using the Football stats filter tool, have a nice day"
39
+
40
+ end
41
+
42
+ def run_query
43
+ request_stats_list
44
+ request_filter_list
45
+ request_sort
46
+ filter_player_data
47
+ sort_player_data
48
+ display_current_player_data
49
+ end
50
+
51
+ def display_current_player_data
52
+ puts stats_list_header
53
+ @player_data.each.with_index do |player, i|
54
+ out = "#{i+1}.\t"
55
+ @stats_list.each do |stat|
56
+ value = player.instance_variable_get("@#{stat}")
57
+ out+= "#{value}#{determine_table_tabs(stat, value)}"
58
+ end
59
+ puts out
60
+ end
61
+ end
62
+
63
+ def sort_player_data
64
+ if(@sort_value == "ASC")
65
+ @player_data.sort!{|player_a, player_b| player_a.instance_variable_get("@#{@sort_stat}") <=> player_b.instance_variable_get("@#{@sort_stat}")}
66
+ elsif(@sort_value == "DESC")
67
+ @player_data.sort!{|player_a, player_b| player_b.instance_variable_get("@#{@sort_stat}") <=> player_a.instance_variable_get("@#{@sort_stat}")}
68
+ end
69
+ end
70
+
71
+ def filter_player_data
72
+ out = []
73
+ @filter_list.each do |filter_entry|
74
+ stat = filter_entry[:stat]
75
+ value = filter_entry[:value]
76
+ compare_type = filter_entry[:compare_type]
77
+
78
+ @player_data.each do |player_entry|
79
+ entry_value = player_entry.instance_variable_get("@#{stat}")
80
+ if(entry_value!=nil)
81
+ if(compare_type == ">" && entry_value>value)
82
+ out.push(player_entry)
83
+ end
84
+ if(compare_type == "<" && entry_value<value)
85
+ out.push(player_entry)
86
+ end
87
+ if(compare_type == "=" && entry_value==value)
88
+ out.push(player_entry)
89
+ end
90
+ end
91
+ end
92
+ @player_data = out
93
+ out = []
94
+ end
95
+ end
96
+
97
+ def request_year
98
+ Player.delete_all
99
+ puts "Please select a year to load"
100
+ year = gets.chomp.to_i
101
+
102
+ begin
103
+ Player.import(year)
104
+ @player_data = Player.all
105
+ rescue OpenURI::HTTPError => error
106
+ if error.message == '404 Not Found'
107
+ puts "Invalid year selection"
108
+ request_year
109
+ else
110
+ raise error
111
+ end
112
+ end
113
+ end
114
+
115
+ def stats_list_header
116
+ out ="\tname\t\t\t"
117
+ tab_length = 8;
118
+
119
+ @stats_list.each do |entry|
120
+ if(entry!=:name)
121
+ out += entry.to_s
122
+ out+="\t"
123
+ end
124
+ end
125
+ out
126
+ end
127
+
128
+ def determine_table_tabs(stat_symbol, value)#returns the appropriate number of tabs for a given table entry
129
+ value_length = value.to_s.length
130
+ stat_symbol_length = stat_symbol.to_s.length
131
+ tab_length = 8;
132
+ name_defaults_tabs =3;
133
+ num_tabs = 0;
134
+ if(stat_symbol == :name)
135
+ num_tabs = name_defaults_tabs-(value_length)/tab_length
136
+ else
137
+ num_tabs = 1 + (stat_symbol_length)/tab_length - (value_length)/tab_length
138
+ end
139
+
140
+ out = ""
141
+
142
+ num_tabs.times{out += "\t"}
143
+ return out
144
+ end
145
+
146
+ def request_stats_list
147
+ puts "Give a list of the stats you would like display along with the player name (e.g. team, sacks, tackles)"
148
+ stats_input = gets.chomp
149
+ stats_interpreted = interpret_stat_selection(stats_input)
150
+ if(stats_interpreted[:invalid_data].length>0)
151
+ puts "The following entries were invalid and will be discarded"
152
+ stats_interpreted[:invalid_data].each do |entry|
153
+ puts entry
154
+ end
155
+ puts "Would you like to repeat this step? (y/n)"
156
+ input = gets.chomp
157
+ if(input == "y")
158
+ request_stats_list
159
+ else
160
+ @stats_list = stats_interpreted[:valid_data]
161
+ end
162
+ else
163
+ @stats_list = stats_interpreted[:valid_data]
164
+ end
165
+ end
166
+
167
+ def request_filter_list
168
+ puts "Give a list of conditions that should be met with the player to be included (e.g sacks > 8, pass_touchdowns > 25, team = SEA)"
169
+ filters_input = gets.chomp
170
+ filters_interpreted = interpret_filter_selection(filters_input)
171
+ if(filters_interpreted[:invalid_data].length>0)
172
+ puts "The following entries were invalid and will be discarded"
173
+ filters_interpreted[:invalid_data].each do |entry|
174
+ puts entry[:string]
175
+ end
176
+ puts "Would you like to repeat this step? (y/n)"
177
+ input = gets.chomp
178
+ if(input == "y")
179
+ request_filter_list
180
+ else
181
+ @filter_list = filters_interpreted[:valid_data]
182
+ end
183
+ else
184
+ @filter_list = filters_interpreted[:valid_data]
185
+ end
186
+
187
+ end
188
+
189
+ def request_sort
190
+ puts "How would you like to sort the data, give a stat and ASC or DESC"
191
+ sort_input = gets.chomp
192
+ sort_interpreted = interpret_sort_selection(sort_input)
193
+ if(sort_interpreted[:return_type] == "invalid_data")
194
+ "Your entry was invlalid and will be discarded"
195
+ puts "Would you like to repeat this step? (y/n)"
196
+ if(input == "y")
197
+ request_sort
198
+ else #default to sort by name ascending
199
+ @sort_stat = "name"
200
+ @sort_value = "ASC"
201
+ end
202
+
203
+ else
204
+ @sort_stat = sort_interpreted[:sort_stat]
205
+ @sort_value = sort_interpreted[:sort_value]
206
+ end
207
+
208
+ end
209
+
210
+ def interpret_stat_selection(input)
211
+ out = input.strip.split(/[\s,]+/)
212
+ out.unshift("name")
213
+ valid_entries = []
214
+ invalid_entries = []
215
+
216
+ out.each do |attribute|
217
+ if(allows_atribute?(attribute))
218
+ valid_entries.push(attribute.to_sym)
219
+ else
220
+ invalid_entries.push(attribute)
221
+ end
222
+ end
223
+
224
+ return {
225
+ :valid_data => valid_entries.uniq,
226
+ :invalid_data => invalid_entries.uniq
227
+ }
228
+
229
+ end
230
+
231
+ def interpret_filter_selection(input)
232
+ list = input.strip.split(/,+/)
233
+ out = []
234
+ valid_entries = []
235
+ invalid_entries = []
236
+ list.each do |entry|
237
+ hash = {}
238
+ compare_type = entry.match(/[=<>]/)
239
+ if(compare_type == nil)#indicates no compare operator (>, <, =) was found
240
+ hash[:type] = "invalid compare statement"
241
+ hash[:string] = entry
242
+ else
243
+ compare_type = compare_type[0]
244
+ entry_data = entry.gsub(/\s+/, "").split(compare_type)
245
+ if(entry_data.length != 2)#indicates either no stat, no value or too many compare operators
246
+ hash[:type] = "invalid compare statement"
247
+ hash[:string] = entry
248
+ elsif(!allows_atribute?(entry_data[0]))
249
+ hash[:type] = "invalid compare statement"
250
+ hash[:string] = entry
251
+ else
252
+ hash[:type] = "valid compare statement"
253
+ hash[:compare_type] = compare_type
254
+ hash[:stat] = entry_data[0].gsub(/\s+/, "").to_sym
255
+ hash[:value] = entry_data[1].gsub(/\s+/, "").to_f
256
+ end
257
+ end
258
+ if(hash[:type] == "invalid compare statement")
259
+ invalid_entries.push(hash)
260
+ elsif(hash[:type] == "valid compare statement")
261
+ valid_entries.push(hash)
262
+ end
263
+ out.push(hash[:type] == "invalid compare statement")
264
+ end
265
+ return {
266
+ :valid_data => valid_entries,
267
+ :invalid_data => invalid_entries
268
+ }
269
+ end
270
+
271
+ def interpret_sort_selection(input)
272
+ out = input.strip.split(/[\s,]+/)
273
+ output_data = {}
274
+ if(out.length>2 || !allows_atribute?(out[0]))
275
+ output_data[:return_type] = "invalid data"
276
+ elsif(out.length == 2)
277
+ sort_priority = out[1].upcase
278
+ if(sort_priority != "ASC" && sort_priority != "DESC")
279
+ output_data[:return_type] = "invalid data"
280
+ else
281
+ output_data[:return_type] = "valid data"
282
+ output_data[:sort_stat] = out[0].to_sym
283
+ output_data[:sort_value] = sort_priority
284
+ end
285
+ elsif(out.length == 1)
286
+ output_data[:sort_stat] = out[0].to_sym
287
+ output_data[:sort_value] = "ASC"
288
+ else
289
+ output_data[:sort_stat] = :name
290
+ output_data[:sort_value] = "ASC"
291
+ end
292
+ output_data
293
+ end
294
+
295
+ def allows_atribute?(attribute)
296
+ first_data_instance = player_data[0]
297
+ first_data_instance.has_attribute?(attribute)
298
+ end
299
+
300
+ def print_allowable_attributes
301
+ first_data_instance = player_data[0]
302
+ puts "The following attributes exist in this data set"
303
+ puts "\n"
304
+ current_string = "";
305
+ first_data_instance.class.allowable_attributes.each_with_index do |attribute, i|
306
+ puts attribute
307
+ end
308
+ puts "\n"
309
+ end
310
+
311
+ end
@@ -0,0 +1,49 @@
1
+ require 'open-uri'
2
+
3
+
4
+ class DataScraper
5
+
6
+ def self.scrape_data(url, stat_type)
7
+ html = Nokogiri::HTML(URI.open(url))
8
+ data = html.css('tr:not(.thead)')
9
+ out = []
10
+ data.each do |entry|
11
+ name = entry.css('td[data-stat="player"]').text
12
+ hash = {}
13
+ if(name != "")
14
+ hash[:name] = entry.css('td[data-stat="player"]').text.match(/.*[a-zA-Z]/)[0]#remove excess whitespace and extra characters
15
+ hash[:age] = entry.css('td[data-stat="age"]').text.to_i
16
+ hash[:position] = entry.css('td[data-stat="pos"]').text.upcase
17
+ hash[:team] = entry.css('td[data-stat="team"]').text
18
+ if(stat_type == "passing")
19
+ hash[:pass_completions] = entry.css('td[data-stat="pass_cmp"]').text.to_f
20
+ hash[:pass_attempts] = entry.css('td[data-stat="pass_att"]').text.to_f
21
+ hash[:pass_completion_percentage] = entry.css('td[data-stat="pass_cmp_perc"]').text.to_f
22
+ hash[:pass_touchdowns] = entry.css('td[data-stat="pass_td"]').text.to_f
23
+ hash[:interceptions_thrown] = entry.css('td[data-stat="pass_int"]').text.to_f
24
+ hash[:pass_yards] = entry.css('td[data-stat="pass_yds"]').text.to_f
25
+ hash[:yards_per_pass_attempt] = entry.css('td[data-stat="pass_yds_per_att"]').text.to_f
26
+ elsif(stat_type == "rushing")
27
+ hash[:carries] = entry.css('td[data-stat="rush_att"]').text.to_f
28
+ hash[:rush_yards] = entry.css('td[data-stat="rush_yds"]').text.to_f
29
+ hash[:rush_yards_per_attempt] = entry.css('td[data-stat="rush_yds_per_att"]').text.to_f
30
+ hash[:rush_touchdowns] = entry.css('td[data-stat="rush_td"]').text.to_f
31
+ elsif(stat_type == "receiving")
32
+ hash[:receptions] = entry.css('td[data-stat="rec"]').text.to_f
33
+ hash[:receiving_yards] = entry.css('td[data-stat="rec_yds"]').text.to_f
34
+ hash[:receiving_yards_per_catch] = entry.css('td[data-stat="rec_yds_per_att"]').text.to_f
35
+ hash[:receiving_touchdowns] = entry.css('td[data-stat="rec_td"]').text.to_f
36
+ elsif(stat_type == "defense")
37
+ hash[:interceptions_caught] = entry.css('td[data-stat="def_int"]').text.to_f
38
+ hash[:passes_defended] = entry.css('td[data-stat="pass_defended"]').text.to_f
39
+ hash[:tackles] = entry.css('td[data-stat="tackles_combined"]').text.to_f
40
+ hash[:sacks] = entry.css('td[data-stat="sacks"]').text.to_f
41
+ hash[:tackles_for_loss] = entry.css('td[data-stat="tackles_loss"]').text.to_f
42
+ hash[:quarterback_hits] = entry.css('td[data-stat="qn_hits"]').text.to_f
43
+ end
44
+ out.push(hash)
45
+ end
46
+ end
47
+ return out
48
+ end
49
+ end
@@ -0,0 +1,8 @@
1
+ require "footballfiltertool/version"
2
+ require_relative "./command_line_interface"
3
+ module Footballfiltertool
4
+ class Error < StandardError; end
5
+ def self.run
6
+ CommandLineInterface.run
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ module Footballfiltertool
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,137 @@
1
+ require_relative "./data_scraper.rb"
2
+
3
+ class Player
4
+ @@allowable_attributes = [
5
+ "name",
6
+ "age",
7
+ "position",
8
+ "team",
9
+ "pass_completions",
10
+ "pass_attempts",
11
+ "pass_completion_percentage",
12
+ "pass_touchdowns",
13
+ "interceptions_thrown",
14
+ "pass_yards",
15
+ "yards_per_pass_attempt",
16
+ "carries",
17
+ "rush_yards",
18
+ "rush_yards_per_attempt",
19
+ "rush_touchdowns",
20
+ "receptions",
21
+ "receiving_yards",
22
+ "receiving_yards_per_catch",
23
+ "receiving_touchdowns",
24
+ "sacks",
25
+ "interceptions_caught",
26
+ "passes_defended",
27
+ "tackles",
28
+ "tackles_for_loss",
29
+ "quarterback_hits",
30
+ ]
31
+
32
+ @@allowable_attributes.each do |attribute|
33
+ attr_accessor attribute.to_sym
34
+ end
35
+
36
+ def self.allowable_attributes
37
+ @@allowable_attributes
38
+ end
39
+
40
+ def has_attribute?(attribute)
41
+ self.class.allowable_attributes.include?(attribute)
42
+ end
43
+
44
+ @@all = []
45
+
46
+ def initialize(data_hash)
47
+ data_hash.each {|key, value| self.send(("#{key}="), value)}
48
+ @@all.push(self)
49
+ end
50
+
51
+ def self.all
52
+ return @@all
53
+ end
54
+
55
+ def self.delete_all
56
+ all.clear
57
+ end
58
+
59
+ def self.find_by_name (name) #returns array of results if multipe are found
60
+ out = nil
61
+ all.select do |player|
62
+ player.name == name
63
+ end
64
+ if(out == nil)
65
+ out_data = {
66
+ :type => "No Results",
67
+ :data => nil
68
+ }
69
+ elsif(out.size == 1)
70
+ out_data = {
71
+ :type => "Unique Result",
72
+ :data => out[0]
73
+ }
74
+ elsif(out.size > 1)
75
+ out_data = {
76
+ :type => "Multiple Results",
77
+ :data => out
78
+ }
79
+ end
80
+ out_data
81
+ end
82
+
83
+ def self.find(name, team)
84
+ out = nil
85
+ all.each do |player|
86
+ if(player.name == name && player.team == team)
87
+ out = player
88
+ end
89
+ end
90
+ return out
91
+
92
+ end
93
+
94
+
95
+
96
+ def self.create_or_update(data_hash)
97
+ name = data_hash[:name]
98
+ team = data_hash[:team]
99
+ find_player = Player.find(name, team)
100
+ if(find_player == nil)
101
+ find_player = Player.new(data_hash)
102
+ else
103
+ data_hash.each do |key, value|
104
+ current_value = find_player.instance_variable_get("@#{key}")
105
+ if(current_value == 0 || current_value = "" || current_value == nil || current_value == 0.0)#ensures not overiding good data
106
+ find_player.send(("#{key}="), value)
107
+ end
108
+ end
109
+ end
110
+ return find_player
111
+ end
112
+
113
+ def self.display_all
114
+ all.each do|player|
115
+ if(player.team = "SEA")
116
+ puts "#{player.name}, #{player.position}, #{player.team}"
117
+ end
118
+ end
119
+ end
120
+
121
+ def self.import(year)
122
+ url_passing = "https://www.pro-football-reference.com/years/#{year}/passing.htm"
123
+ url_rushing = "https://www.pro-football-reference.com/years/#{year}/rushing.htm"
124
+ url_receiving = "https://www.pro-football-reference.com/years/#{year}/receiving.htm"
125
+ url_defense = "https://www.pro-football-reference.com/years/#{year}/defense.htm"
126
+
127
+ data_passing = DataScraper.scrape_data(url_passing, "passing")
128
+ data_rushing = DataScraper.scrape_data(url_rushing, "rushing")
129
+ data_receiving = DataScraper.scrape_data(url_receiving, "receiving")
130
+ data_defense = DataScraper.scrape_data(url_defense, "defense")
131
+
132
+ data_passing.each{|entry| Player.create_or_update(entry)}
133
+ data_rushing.each{|entry| Player.create_or_update(entry)}
134
+ data_receiving.each{|entry| Player.create_or_update(entry)}
135
+ data_defense.each{|entry| Player.create_or_update(entry)}
136
+ end
137
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: footballfiltertool
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Michael Fuget
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-12-31 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Pulls stats from the current football season and generates filtered and
14
+ sorted lists basaed on user input
15
+ email:
16
+ - michaelfuget@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - bin/console
22
+ - bin/main
23
+ - bin/setup
24
+ - lib/command_line_interface.rb
25
+ - lib/data_scraper.rb
26
+ - lib/footballfiltertool.rb
27
+ - lib/footballfiltertool/version.rb
28
+ - lib/player.rb
29
+ homepage: https://github.com/mkfuget
30
+ licenses:
31
+ - MIT
32
+ metadata:
33
+ allowed_push_host: https://rubygems.org/
34
+ homepage_uri: https://github.com/mkfuget
35
+ source_code_uri: https://github.com/mkfuget
36
+ post_install_message:
37
+ rdoc_options: []
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: 2.3.0
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ requirements: []
51
+ rubygems_version: 3.1.2
52
+ signing_key:
53
+ specification_version: 4
54
+ summary: Football Stats filter tool 1.0
55
+ test_files: []