mlb_terminal 0.0.2 → 0.0.3

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.
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