footballfiltertool 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ 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: []