mlb_terminal 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mlb_terminal (0.0.1)
4
+ mlb_terminal (0.0.2)
5
5
  activesupport (~> 3.2.8)
6
6
  commander (~> 4.1.2)
7
7
  nokogiri (~> 1.5.5)
data/README.md CHANGED
@@ -9,22 +9,23 @@ Don't you just wish you could have a little terminal app to stream out real-time
9
9
  ### Syntax
10
10
 
11
11
  #### Commands
12
-
13
- * help: Display global or [command] help documentation.
14
- * list: Print out a list of scheduled games for the specified date
12
+
13
+ * help: Display global or [command] help documentation.
14
+ * games: Print out a list of scheduled games for the specified date
15
+ * game: Print out play-by-play events for a specific game. Use `--pitches` to output realtime pitch trajectory data.
15
16
 
16
17
  #### Global options
17
-
18
+
18
19
  * -h, --help: Display help documentation
19
-
20
+
20
21
  * -v, --version: Display version information
21
-
22
+
22
23
  * -t, --trace: Display backtrace when an error occurs
23
24
 
24
25
  ### Examples
25
26
 
26
27
  > # List todays games
27
- > mlb list
28
+ > mlb games
28
29
  0 Red Sox (69-88) @ Orioles (90-67) 7:05 ET (F) 1-9
29
30
  1 Reds (95-62) @ Pirates (76-81) 7:05 ET (F) 1-0
30
31
  2 Royals (70-87) @ Indians (66-91) 7:05 ET (F) 5-8
@@ -41,6 +42,33 @@ Don't you just wish you could have a little terminal app to stream out real-time
41
42
  13 Giants (92-65) @ Padres (74-83) 10:05 ET (F) 3-1
42
43
  14 Rockies (62-95) @ Dodgers (82-75) 10:10 ET (F) 0-8
43
44
 
44
- # How did the Nationals do on May 30th of this year?
45
- > mlb list --date 2012-05-30 | grep -i nationals | cut -f 2,4
46
- Nationals (29-21) @ Marlins (29-22) 3-5
45
+ # How did the Nationals do on 5/30/2012?
46
+ > mlb games --date 2012-05-30 | grep -i nationals | cut -f 2,4
47
+ Nationals (29-21) @ Marlins (29-22) 3-5
48
+
49
+ > # How did Bryce Harper do tonight?
50
+ > mlb games | grep Nationals | cut -f 1 | xargs mlb game | grep Harper
51
+ 2012-09-29 19:18:00 1 2 1/2/1 Bryce Harper singles on a soft line drive to center fielder Jon Jay.
52
+ 2012-09-29 19:20:11 1 3 1/2/1 Ryan Zimmerman doubles (36) on a fly ball to left fielder Matt Holliday. Bryce Harper to 3rd.
53
+ 2012-09-29 19:26:56 1 5 0/0/1 Play reviewed and overturned: Michael Morse hits a grand slam (17) to right field. Bryce Harper scores. Ryan Zimmerman scores. Adam LaRoche scores.
54
+ 2012-09-29 19:48:35 2 14 3/2/3 Bryce Harper grounds out, second baseman Skip Schumaker to first baseman Allen Craig.
55
+ 2012-09-29 19:56:08 2 17 3/2/2 Carlos Beltran lines out to center fielder Bryce Harper.
56
+ 2012-09-29 20:09:19 3 23 3/2/1 Pete Kozma flies out to center fielder Bryce Harper.
57
+ 2012-09-29 20:38:38 5 36 3/3/1 Bryce Harper called out on strikes.
58
+ 2012-09-29 21:15:16 7 51 2/2/1 Bryce Harper singles on a line drive to left fielder Matt Holliday.
59
+ 2012-09-29 21:19:08 7 53 1/1/2 Adam LaRoche singles on a sharp line drive to first baseman Allen Craig. Bryce Harper to 3rd.
60
+ 2012-09-29 21:24:34 7 55 0/1/1 Yadier Molina flies out to center fielder Bryce Harper.
61
+ 2012-09-29 21:37:46 7 61 1/2/2 Matt Carpenter flies out to center fielder Bryce Harper.
62
+ 2012-09-29 22:10:49 9 71 1/0/2 Bryce Harper doubles (25) on a line drive to left fielder Matt Holliday.
63
+ 2012-09-29 22:21:20 9 76 0/0/2 Jon Jay out on a sacrifice fly to center fielder Bryce Harper. Pete Kozma scores.
64
+
65
+ > # Visualize pitch speed for Gio Gonzalez on his full game on August 8, 2012.
66
+ > mlb games --date 2012-08-08 \
67
+ | grep Nationals \
68
+ | cut -f 1 \
69
+ | xargs mlb game --pitches --date 2012-08-08 \
70
+ | awk 'BEGIN{FS="\t"}{ if ($4=="Gio Gonzalez") print}' \
71
+ | cut -f 1,9,6 \
72
+ | Rscript -e 'library(ggplot2); library(scales); d <- read.csv("stdin", header=F, sep="\\t", col.names=c("time", "type", "pitch_speed")); png("gio-pitch-speed.png"); ggplot(d, aes(x=as.POSIXct(time), y=pitch_speed, colour=type, shape=type)) + geom_point() + opts(title="Pitch Speed Over Time for Gio Gonzalez") + scale_x_datetime(breaks=date_breaks("1 hour"), minor_breaks=date_breaks("15 min"), labels=date_format("%H:%M")) + scale_colour_discrete(name = "Pitch Type") + scale_shape_discrete(name = "Pitch Type") + xlab("Time") + ylab("Pitch Speed"); dev.off()'
73
+ > open gio-pitch-speed.png
74
+ ![gio-pitch-speed.png](http://i.imgur.com/WFw3J.png)
data/bin/mlb CHANGED
@@ -17,7 +17,7 @@ command :games do |c|
17
17
  c.option '--date STRING', String, 'List games for specified dates (Default: today)'
18
18
  c.action do |args, options|
19
19
  options.default :date => Time.now.to_date.to_s
20
- MLBTerminal::Client.list_games(Date.parse options.date).each_with_index do |game, index|
20
+ MLBTerminal::Game.list(Date.parse options.date).each_with_index do |game, index|
21
21
  puts [
22
22
  index,
23
23
  "#{game[:away_team][:name]} " \
@@ -36,6 +36,7 @@ command :game do |c|
36
36
  c.description = 'Print play-by-play action for the corresponding game. Columns: time, inning, balls/strikes/outs/description.'
37
37
  c.example 'description', 'command example'
38
38
  c.option '--date STRING', String, 'Specify the date for the corresponding game number.'
39
+ c.option '--pitches', 'Print pitch-by-pitch action'
39
40
  c.action do |args, options|
40
41
  options.default :date => Time.now.to_date.to_s
41
42
 
@@ -47,23 +48,68 @@ command :game do |c|
47
48
  end
48
49
 
49
50
  game_number = args.first.to_i
50
- game_info = MLBTerminal::Client.list_games(date)[game_number]
51
+ game_info = MLBTerminal::Game.list(date)[game_number]
51
52
 
52
53
  if game_info.nil?
53
54
  puts "Error: Game does not exist"
54
55
  break
55
56
  end
56
57
 
57
- game = MLBTerminal::Client.new(game_info[:game_id])
58
+ game = MLBTerminal::Game.new(game_info[:game_id])
58
59
 
59
- game.events.each do |event|
60
- puts [
61
- event[:time].getlocal.strftime("%Y-%m-%d %H:%M:%S"),
62
- event[:inning],
63
- event[:num],
64
- "#{event[:balls]}/#{event[:strikes]}/#{event[:outs]}",
65
- event[:desc]].join("\t")
60
+ begin
61
+ if options.pitches
62
+ game.pitches.each do |pitch|
63
+ puts [
64
+ pitch[:time].getlocal.strftime("%Y-%m-%d %H:%M:%S"),
65
+ pitch[:inning],
66
+ pitch[:inning_loc],
67
+ pitch[:pitcher],
68
+ pitch[:batter],
69
+ pitch[:type],
70
+ pitch[:x],
71
+ pitch[:y],
72
+ pitch[:start_speed],
73
+ pitch[:end_speed],
74
+ pitch[:sz_top],
75
+ pitch[:sz_bot],
76
+ pitch[:pfx_x],
77
+ pitch[:pfx_z],
78
+ pitch[:px],
79
+ pitch[:pz],
80
+ pitch[:x0],
81
+ pitch[:y0],
82
+ pitch[:z0],
83
+ pitch[:vx0],
84
+ pitch[:vy0],
85
+ pitch[:vz0],
86
+ pitch[:ax],
87
+ pitch[:ay],
88
+ pitch[:az],
89
+ pitch[:break_y],
90
+ pitch[:break_angle],
91
+ pitch[:break_length],
92
+ pitch[:pitch_type],
93
+ pitch[:type_confidence],
94
+ pitch[:zone],
95
+ pitch[:nasty],
96
+ pitch[:spin_dir],
97
+ pitch[:spin_rate],
98
+ pitch[:cc],
99
+ pitch[:mt]].join("\t")
100
+ end
101
+ else
102
+ game.events.each do |event|
103
+ puts [
104
+ event[:time].getlocal.strftime("%Y-%m-%d %H:%M:%S"),
105
+ event[:inning],
106
+ event[:num],
107
+ "#{event[:balls]}/#{event[:strikes]}/#{event[:outs]}",
108
+ event[:desc]].join("\t")
109
+ end
110
+ end
111
+ rescue SystemExit, Interrupt
112
+ # Do nothing if a user issues ctrl-c.
66
113
  end
67
114
  end
68
115
  end
69
-
data/lib/mlb_terminal.rb CHANGED
@@ -1,2 +1,2 @@
1
1
  require 'mlb_terminal/version'
2
- require 'mlb_terminal/client'
2
+ require 'mlb_terminal/game'
@@ -0,0 +1,153 @@
1
+ require 'date'
2
+ require 'nokogiri'
3
+ require 'open-uri'
4
+ require 'active_support/core_ext/integer/inflections'
5
+
6
+ module MLBTerminal
7
+ MLB_BASE_URL = 'http://gd2.mlb.com/components/game/mlb'
8
+
9
+ class Game
10
+ def self.list(date = Time.now.to_date)
11
+ doc = Nokogiri::HTML(open "#{base_url_for_date date}/epg.xml")
12
+
13
+ doc.xpath("//epg/game").map{|game|
14
+ {:home_team => {
15
+ :name => game["home_team_name"],
16
+ :wins => game["home_win"],
17
+ :losses => game["home_loss"]},
18
+ :away_team => {
19
+ :name => game["away_team_name"],
20
+ :wins => game["away_win"],
21
+ :losses => game["away_loss"]},
22
+ :score => {
23
+ :home => game["home_team_runs"],
24
+ :away => game["away_team_runs"]},
25
+ :starts => "#{game["time"]} #{game["time_zone"]}",
26
+ :status => "#{game["status"]}" \
27
+ "#{game["status"] == "In Progress" ?
28
+ ", #{game["top_inning"]=="Y" ? "Top" : "Bot"} of #{game["inning"].to_i.ordinalize}" :
29
+ ""}",
30
+ :game_id => game["gameday"]}}
31
+ end
32
+
33
+ def initialize(gameday)
34
+ @gameday = gameday
35
+ year, month, day, game_name = /([0-9]{4})_([0-9]{2})_([0-9]{2})_(.*)/.match(@gameday).to_a.slice(1,4)
36
+ @base_url = "#{Game.base_url_for_date(Date.new(year.to_i, month.to_i, day.to_i))}/gid_#{@gameday}"
37
+ end
38
+
39
+ def events(delay = 5, &block)
40
+ last_atbat = 0
41
+
42
+ Enumerator.new do |y|
43
+ begin
44
+ doc = Nokogiri::HTML(open "#{@base_url}/game_events.xml")
45
+
46
+ doc.xpath("//game/inning[*/atbat/@num > #{last_atbat}]").each do |inning|
47
+ inning.xpath("*/atbat").each do |at_bat|
48
+ y.yield({
49
+ :inning => inning["num"],
50
+ :inning_loc => at_bat.xpath("..").first.name.camelcase,
51
+ :num => at_bat["num"],
52
+ :balls => at_bat["b"],
53
+ :strikes => at_bat["s"],
54
+ :outs => at_bat["o"],
55
+ :time => Time.parse(at_bat["start_tfs_zulu"], "%Y-%m-%dT%H:%M:%SZ"),
56
+ :desc => at_bat["des"].strip})
57
+ end
58
+ end
59
+
60
+ last_atbat = doc.xpath("//game/inning/*/atbat/@num").map(&:value).map(&:to_i).max
61
+
62
+ sleep delay
63
+
64
+ if doc.xpath("//game").first["ind"] != "F"
65
+ doc = Nokogiri::HTML(open "#{@base_url}/inning/inning_all.xml")
66
+ end
67
+ end while game_status != "F"
68
+ end.each(&block)
69
+ end
70
+
71
+ def pitches(delay = 5, &block)
72
+ last_pitch = 0
73
+ players = player_lookup
74
+ doc = Nokogiri::HTML(open "#{@base_url}/inning/inning_all.xml")
75
+
76
+ Enumerator.new do |y|
77
+ begin
78
+ doc.xpath("//game/inning[*/*/pitch/@id > #{last_pitch}]").each do |inning|
79
+
80
+ inning.xpath("*/atbat").each do |at_bat|
81
+ pitcher = players[at_bat["pitcher"]]
82
+ batter = players[at_bat["batter"]]
83
+
84
+ at_bat.xpath("pitch").each do |pitch|
85
+ y.yield({
86
+ :time => Time.parse(pitch["tfs_zulu"], "%Y-%m-%dT%H:%M:%SZ"),
87
+ :inning => inning["num"],
88
+ :inning_loc => at_bat.xpath("..").first.name.camelcase,
89
+ :pitcher => pitcher,
90
+ :batter => batter,
91
+ :type => pitch["type"],
92
+ :x => pitch["x"],
93
+ :y => pitch["y"],
94
+ :start_speed => pitch["start_speed"],
95
+ :end_speed => pitch["end_speed"],
96
+ :sz_top => pitch["sz_top"],
97
+ :sz_bot => pitch["sz_bot"],
98
+ :pfx_x => pitch["pfx_x"],
99
+ :pfx_z => pitch["pfx_z"],
100
+ :px => pitch["px"],
101
+ :pz => pitch["pz"],
102
+ :x0 => pitch["x0"],
103
+ :y0 => pitch["y0"],
104
+ :z0 => pitch["z0"],
105
+ :vx0 => pitch["vx0"],
106
+ :vy0 => pitch["vy0"],
107
+ :vz0 => pitch["vz0"],
108
+ :ax => pitch["ax"],
109
+ :ay => pitch["ay"],
110
+ :az => pitch["az"],
111
+ :break_y => pitch["break_y"],
112
+ :break_angle => pitch["break_angle"],
113
+ :break_length => pitch["break_length"],
114
+ :pitch_type => pitch["pitch_type"],
115
+ :type_confidence => pitch["type_confidence"],
116
+ :zone => pitch["zone"],
117
+ :nasty => pitch["nasty"],
118
+ :spin_dir => pitch["spin_dir"],
119
+ :spin_rate => pitch["spin_rate"],
120
+ :cc => pitch["cc"],
121
+ :mt => pitch["mt"]})
122
+ end
123
+ end
124
+ end
125
+
126
+ last_pitch = doc.xpath("//game/inning/*/*/pitch/@id").map(&:value).map(&:to_i).max
127
+
128
+ sleep delay
129
+
130
+ if doc.xpath("//game").first["ind"] != "F"
131
+ doc = Nokogiri::HTML(open "#{@base_url}/inning/inning_all.xml")
132
+ end
133
+ end while game_status != "F"
134
+ end.each(&block)
135
+ end
136
+
137
+ def self.base_url_for_date(date = Time.now.to_date)
138
+ "#{MLB_BASE_URL}/year_#{date.year}/month_#{"%02d" % date.month}/day_#{"%02d" % date.day}"
139
+ end
140
+
141
+ private
142
+
143
+ def player_lookup
144
+ doc = Nokogiri::HTML(open( "#{@base_url}/players.xml"))
145
+ Hash[doc.xpath("//game/team/player").map{|x| [x["id"], "#{x["first"]} #{x["last"]}"]}]
146
+ end
147
+
148
+ def game_status
149
+ doc = Nokogiri::HTML(open( "#{@base_url}/linescore.xml"))
150
+ doc.xpath("//game").first["ind"]
151
+ end
152
+ end
153
+ end
@@ -1,3 +1,3 @@
1
1
  module MLBTerminal
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
data/mlb_terminal.gemspec CHANGED
@@ -9,8 +9,8 @@ Gem::Specification.new do |s|
9
9
  s.authors = ["Stefan Novak"]
10
10
  s.email = ["stefan.louis.novak@gmail.com"]
11
11
  s.homepage = "https://github.com/slnovak/mlb_terminal"
12
- s.description = %q{A small terminal app to translate MLB baseball feeds into the terminal for easy manipulation.}
13
- s.summary = %q{Command line interface to keeping track of MLB ballgames}
12
+ s.description = %q{A small terminal app to pipe formatted MLB baseball feeds into the terminal for easy manipulation. Includes access to both historic and real-time baseball feeds including play-by-play and pitch-by-pitch data. Visit the homepage for examples!}
13
+ s.summary = %q{A small terminal app for accessing historic and realtime MLB game data}
14
14
 
15
15
  s.files = `git ls-files`.split("\n")
16
16
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mlb_terminal
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -59,8 +59,9 @@ dependencies:
59
59
  - - ~>
60
60
  - !ruby/object:Gem::Version
61
61
  version: 3.2.8
62
- description: A small terminal app to translate MLB baseball feeds into the terminal
63
- for easy manipulation.
62
+ description: A small terminal app to pipe formatted MLB baseball feeds into the terminal
63
+ for easy manipulation. Includes access to both historic and real-time baseball feeds
64
+ including play-by-play and pitch-by-pitch data. Visit the homepage for examples!
64
65
  email:
65
66
  - stefan.louis.novak@gmail.com
66
67
  executables:
@@ -75,7 +76,7 @@ files:
75
76
  - Rakefile
76
77
  - bin/mlb
77
78
  - lib/mlb_terminal.rb
78
- - lib/mlb_terminal/client.rb
79
+ - lib/mlb_terminal/game.rb
79
80
  - lib/mlb_terminal/version.rb
80
81
  - mlb_terminal.gemspec
81
82
  homepage: https://github.com/slnovak/mlb_terminal
@@ -101,5 +102,5 @@ rubyforge_project:
101
102
  rubygems_version: 1.8.24
102
103
  signing_key:
103
104
  specification_version: 3
104
- summary: Command line interface to keeping track of MLB ballgames
105
+ summary: A small terminal app for accessing historic and realtime MLB game data
105
106
  test_files: []
@@ -1,85 +0,0 @@
1
- require 'date'
2
- require 'nokogiri'
3
- require 'open-uri'
4
- require 'active_support/core_ext/integer/inflections'
5
-
6
- module MLBTerminal
7
- MLB_BASE_URL = 'http://gd2.mlb.com/components/game/mlb'
8
-
9
- class Client
10
- def self.list_games(date = Time.now.to_date)
11
- doc = Nokogiri::HTML(open "#{base_url_for_date date}/epg.xml")
12
- doc.xpath("//epg/game").map{|game|
13
- {:home_team => {
14
- :name => game["home_team_name"],
15
- :wins => game["home_win"],
16
- :losses => game["home_loss"]},
17
- :away_team => {
18
- :name => game["away_team_name"],
19
- :wins => game["away_win"],
20
- :losses => game["away_loss"]},
21
- :score => {
22
- :home => game["home_team_runs"],
23
- :away => game["away_team_runs"]},
24
- :starts => "#{game["time"]} #{game["time_zone"]}",
25
- :status => "#{game["status"]}" \
26
- "#{game["status"] == "In Progress" ?
27
- ", #{game["top_inning"]=="Y" ? "Top" : "Bot"} of #{game["inning"].to_i.ordinalize}" :
28
- ""}",
29
- :game_id => game["gameday"]}}
30
- end
31
-
32
- def initialize(gameday)
33
- @gameday = gameday
34
- year, month, day, game_name = /([0-9]{4})_([0-9]{2})_([0-9]{2})_(.*)/.match(@gameday).to_a.slice(1,4)
35
- @base_url = "#{Client.base_url_for_date(Date.new(year.to_i, month.to_i, day.to_i))}/gid_#{@gameday}"
36
- end
37
-
38
- def events(start_inning = 1, end_inning = 9, delay = 5, &block)
39
- current_inning = start_inning
40
- current_inning_loc= "top"
41
-
42
- innings = ["top", "bottom"]
43
-
44
- Enumerator.new do |y|
45
- while current_inning <= end_inning and not current_inning_loc.nil?
46
- doc = Nokogiri::HTML(open "#{@base_url}/game_events.xml")
47
-
48
- doc.xpath("//game/inning[@num >= #{current_inning}]").each do |inning|
49
-
50
- current_inning_loc ||= "top"
51
-
52
- innings.slice(innings.index(current_inning_loc), 2).each do |inning_loc|
53
- if inning.xpath(inning_loc).nil?
54
- break
55
- else
56
- inning.xpath("#{inning_loc}/atbat").each do |atbat|
57
- y.yield({:inning => current_inning,
58
- :num => atbat["num"],
59
- :balls => atbat["b"],
60
- :strikes => atbat["s"],
61
- :outs => atbat["o"],
62
- :time => Time.parse(atbat["start_tfs_zulu"], "%Y-%m-%dT%H:%M:%SZ"),
63
- :desc => atbat["des"]})
64
- end
65
-
66
- current_inning_loc = innings[innings.index(current_inning_loc) + 1]
67
- end
68
- end
69
- current_inning += 1
70
- end
71
-
72
- # If we had to break early, wait before trying again.
73
- if current_inning != end_inning and not current_inning_loc.nil?
74
- sleep delay
75
- end
76
-
77
- end
78
- end.each(&block)
79
- end
80
-
81
- def self.base_url_for_date(date = Time.now.to_date)
82
- "#{MLB_BASE_URL}/year_#{date.year}/month_#{"%02d" % date.month}/day_#{"%02d" % date.day}"
83
- end
84
- end
85
- end