sports_data_api 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -1
- data/lib/sports_data_api.rb +1 -0
- data/lib/sports_data_api/ncaafb.rb +84 -0
- data/lib/sports_data_api/ncaafb/broadcast.rb +14 -0
- data/lib/sports_data_api/ncaafb/division.rb +14 -0
- data/lib/sports_data_api/ncaafb/game.rb +78 -0
- data/lib/sports_data_api/ncaafb/games.rb +28 -0
- data/lib/sports_data_api/ncaafb/injuries.rb +9 -0
- data/lib/sports_data_api/ncaafb/player.rb +13 -0
- data/lib/sports_data_api/ncaafb/season.rb +25 -0
- data/lib/sports_data_api/ncaafb/team.rb +86 -0
- data/lib/sports_data_api/ncaafb/teams.rb +101 -0
- data/lib/sports_data_api/ncaafb/venue.rb +21 -0
- data/lib/sports_data_api/ncaafb/weather.rb +16 -0
- data/lib/sports_data_api/ncaafb/week.rb +18 -0
- data/lib/sports_data_api/version.rb +1 -1
- data/spec/cassettes/sports_data_api_ncaafb.yml +117 -0
- data/spec/cassettes/sports_data_api_ncaafb_broadcast.yml +1293 -0
- data/spec/cassettes/sports_data_api_ncaafb_game.yml +21491 -0
- data/spec/cassettes/sports_data_api_ncaafb_games.yml +1452 -0
- data/spec/cassettes/sports_data_api_ncaafb_season.yml +18150 -0
- data/spec/cassettes/sports_data_api_ncaafb_team.yml +884 -0
- data/spec/cassettes/sports_data_api_ncaafb_team_hierarchy.yml +1819 -0
- data/spec/cassettes/sports_data_api_ncaafb_venue.yml +1293 -0
- data/spec/cassettes/sports_data_api_ncaafb_weather.yml +1293 -0
- data/spec/cassettes/sports_data_api_nfl_week.yml +18148 -1
- data/spec/lib/sports_data_api/ncaafb/broadcast_spec.rb +19 -0
- data/spec/lib/sports_data_api/ncaafb/game_spec.rb +157 -0
- data/spec/lib/sports_data_api/ncaafb/games_spec.rb +21 -0
- data/spec/lib/sports_data_api/ncaafb/season_spec.rb +31 -0
- data/spec/lib/sports_data_api/ncaafb/team_spec.rb +74 -0
- data/spec/lib/sports_data_api/ncaafb/teams_spec.rb +76 -0
- data/spec/lib/sports_data_api/ncaafb/venue_spec.rb +27 -0
- data/spec/lib/sports_data_api/ncaafb/weather_spec.rb +21 -0
- data/spec/lib/sports_data_api/ncaafb/week_spec.rb +21 -0
- data/spec/lib/sports_data_api/ncaafb_spec.rb +42 -0
- data/spec/spec_helper.rb +2 -0
- data/sports_data_api.gemspec +1 -1
- metadata +56 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 955753065636d3442bf1601d37c3281fd20b3c8b
|
4
|
+
data.tar.gz: 591bae0973f4af66eba5a8449163383839eb841f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aa67111339cc2fceec855ed582f39888f04cffb0ecd8d5af4aa68be62e0197726de446f69e299e99d073c83adfc70a8db6f56a27e886f704933c8c45c938da07
|
7
|
+
data.tar.gz: 7b20aa4149a074f663926b5447c48a7f4d82ac81f500df04b842888362b3124c95391dd0a2988fcb763cab1c9e69fc00ce6bab7d7c0192c0784f372ec8a5bb5a
|
data/.travis.yml
CHANGED
data/lib/sports_data_api.rb
CHANGED
@@ -48,6 +48,7 @@ module SportsDataApi
|
|
48
48
|
|
49
49
|
autoload :Stats, File.join(LIBRARY_PATH, 'stats')
|
50
50
|
autoload :Nfl, File.join(LIBRARY_PATH, 'nfl')
|
51
|
+
autoload :Ncaafb, File.join(LIBRARY_PATH, 'ncaafb')
|
51
52
|
autoload :Nba, File.join(LIBRARY_PATH, 'nba')
|
52
53
|
autoload :Mlb, File.join(LIBRARY_PATH, 'mlb')
|
53
54
|
autoload :Nhl, File.join(LIBRARY_PATH, 'nhl')
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module SportsDataApi
|
2
|
+
module Ncaafb
|
3
|
+
class Exception < ::Exception
|
4
|
+
end
|
5
|
+
|
6
|
+
DIR = File.join(File.dirname(__FILE__), 'ncaafb')
|
7
|
+
BASE_URL = 'http://api.sportsdatallc.org/ncaafb-%{access_level}%{version}'
|
8
|
+
DEFAULT_VERSION = 1
|
9
|
+
SPORT = :ncaafb
|
10
|
+
|
11
|
+
autoload :Division, File.join(DIR, 'division')
|
12
|
+
autoload :Team, File.join(DIR, 'team')
|
13
|
+
autoload :Teams, File.join(DIR, 'teams')
|
14
|
+
autoload :Player, File.join(DIR, 'player')
|
15
|
+
autoload :Game, File.join(DIR, 'game')
|
16
|
+
autoload :Games, File.join(DIR, 'games')
|
17
|
+
autoload :Week, File.join(DIR, 'week')
|
18
|
+
autoload :Season, File.join(DIR, 'season')
|
19
|
+
autoload :Venue, File.join(DIR, 'venue')
|
20
|
+
autoload :Broadcast, File.join(DIR, 'broadcast')
|
21
|
+
autoload :Weather, File.join(DIR, 'weather')
|
22
|
+
|
23
|
+
##
|
24
|
+
# Fetches Ncaafb season schedule for a given year and season
|
25
|
+
def self.schedule(year, season, version = DEFAULT_VERSION)
|
26
|
+
season = season.to_s.upcase.to_sym
|
27
|
+
raise SportsDataApi::Ncaafb::Exception.new("#{season} is not a valid season") unless Season.valid?(season)
|
28
|
+
|
29
|
+
response = self.response_json(version, "/#{year}/#{season}/schedule.json")
|
30
|
+
|
31
|
+
return Season.new(response)
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Fetches Ncaafb boxscore for a given game
|
36
|
+
def self.boxscore(year, season, week, home, away, version = DEFAULT_VERSION)
|
37
|
+
season = season.to_s.upcase.to_sym
|
38
|
+
raise SportsDataApi::Ncaafb::Exception.new("#{season} is not a valid season") unless Season.valid?(season)
|
39
|
+
|
40
|
+
response = self.response_json(version, "/#{year}/#{season}/#{week}/#{away}/#{home}/boxscore.json")
|
41
|
+
|
42
|
+
return Game.new(year, season, week, response)
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# Fetches statistics for a given Ncaafb game
|
47
|
+
def self.game_statistics(year, season, week, home, away, version = DEFAULT_VERSION)
|
48
|
+
season = season.to_s.upcase.to_sym
|
49
|
+
raise SportsDataApi::Ncaafb::Exception.new("#{season} is not a valid season") unless Season.valid?(season)
|
50
|
+
|
51
|
+
response = self.response_json(version, "/#{year}/#{season}/#{week}/#{away}/#{home}/statistics.json")
|
52
|
+
|
53
|
+
return Game.new(year, season, week, response)
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Fetches all Ncaafb teams
|
58
|
+
def self.teams(division, version = DEFAULT_VERSION)
|
59
|
+
raise SportsDataApi::Ncaafb::Exception.new("#{division} is not a valid season") unless Division.valid?(division)
|
60
|
+
response = self.response_json(version, "/teams/#{division}/hierarchy.json")
|
61
|
+
|
62
|
+
return Teams.new(response)
|
63
|
+
end
|
64
|
+
|
65
|
+
##
|
66
|
+
# Fetches Ncaafb weekly schedule for a given year, season and week
|
67
|
+
def self.weekly(year, season, week, version = DEFAULT_VERSION)
|
68
|
+
season = season.to_s.upcase.to_sym
|
69
|
+
raise SportsDataApi::Ncaafb::Exception.new("#{season} is not a valid season") unless Season.valid?(season)
|
70
|
+
|
71
|
+
response = self.response_json(version, "/#{year}/#{season}/#{week}/schedule.json")
|
72
|
+
|
73
|
+
return Games.new(year, season, week, response)
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def self.response_json(version, url)
|
79
|
+
base_url = BASE_URL % { access_level: SportsDataApi.access_level(SPORT), version: version }
|
80
|
+
response = SportsDataApi.generic_request("#{base_url}#{url}", SPORT)
|
81
|
+
MultiJson.load(response.to_s)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module SportsDataApi
|
2
|
+
module Ncaafb
|
3
|
+
class Broadcast
|
4
|
+
attr_reader :network, :satellite, :internet
|
5
|
+
def initialize(broadcast_hash)
|
6
|
+
if broadcast_hash
|
7
|
+
@network = broadcast_hash['network']
|
8
|
+
@satellite = broadcast_hash['satellite'] || ''
|
9
|
+
@internet = broadcast_hash['internet']
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module SportsDataApi
|
2
|
+
module Ncaafb
|
3
|
+
class Division
|
4
|
+
##
|
5
|
+
# Check if the requested division is a valid
|
6
|
+
# Ncaafb division type.
|
7
|
+
#
|
8
|
+
# The only valid types are: :REG
|
9
|
+
def self.valid?(division)
|
10
|
+
[:FBS,:FCS, :D2, :D3, :NAIA, :USCAA].include?(division)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module SportsDataApi
|
2
|
+
module Ncaafb
|
3
|
+
class Game
|
4
|
+
attr_reader :id, :scheduled, :home, :home_team, :away,
|
5
|
+
:away_team, :status, :quarter, :clock, :venue, :broadcast, :weather,
|
6
|
+
:year, :season, :week
|
7
|
+
|
8
|
+
def initialize(year, season, week, game_hash)
|
9
|
+
@year = year
|
10
|
+
@season = season
|
11
|
+
@week = week
|
12
|
+
|
13
|
+
@id = game_hash['id']
|
14
|
+
@scheduled = Time.parse game_hash['scheduled']
|
15
|
+
@home = game_hash['home'] || game_hash['home_team']['id']
|
16
|
+
@away = game_hash['away'] || game_hash['away_team']['id']
|
17
|
+
@status = game_hash['status']
|
18
|
+
@quarter = game_hash['quarter'].to_i
|
19
|
+
@clock = game_hash['clock']
|
20
|
+
|
21
|
+
@home_team = Team.new(game_hash['home_team'])
|
22
|
+
@away_team = Team.new(game_hash['away_team'])
|
23
|
+
@venue = Venue.new(game_hash['venue'])
|
24
|
+
@broadcast = Broadcast.new(game_hash['broadcast'])
|
25
|
+
@weather = Weather.new(game_hash['weather'])
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Wrapper for Ncaafb.statistics
|
30
|
+
def statistics
|
31
|
+
Ncaafb.game_statistics(year, season, week, home, away, 1)
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Wrapper for Ncaafb.summary
|
36
|
+
# TODO
|
37
|
+
def summary
|
38
|
+
raise NotImplementedError
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
# Wrapper for Ncaafb.pbp (Ncaafb.play_by_play)
|
43
|
+
# TODO
|
44
|
+
def pbp
|
45
|
+
raise NotImplementedError
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Wrapper for Ncaafb.boxscore these helper methods are used
|
50
|
+
# to provide similar functionality as the links attribute
|
51
|
+
# found in the weekly schedule example.
|
52
|
+
def boxscore
|
53
|
+
Ncaafb.boxscore(year, season, week, home, away, 1)
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Wrapper for Ncaafb.roster
|
58
|
+
# TODO
|
59
|
+
def roster
|
60
|
+
raise NotImplementedError
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# Wrapper for Ncaafb.injuries
|
65
|
+
# TODO
|
66
|
+
def injuries
|
67
|
+
raise NotImplementedError
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# Wrapper for Ncaafb.depthchart
|
72
|
+
# TODO
|
73
|
+
def depthchart
|
74
|
+
raise NotImplementedError
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module SportsDataApi
|
2
|
+
module Ncaafb
|
3
|
+
class Games
|
4
|
+
include Enumerable
|
5
|
+
attr_reader :games, :year, :season, :week
|
6
|
+
|
7
|
+
def initialize(year, season, week, games_hash)
|
8
|
+
@year = year
|
9
|
+
@season = season
|
10
|
+
@week = week
|
11
|
+
|
12
|
+
@games = games_hash['games'].map do |game_hash|
|
13
|
+
Game.new(@year, @season, @week, game_hash)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def each &block
|
18
|
+
@games.each do |game|
|
19
|
+
if block_given?
|
20
|
+
block.call game
|
21
|
+
else
|
22
|
+
yield game
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module SportsDataApi
|
2
|
+
module Ncaafb
|
3
|
+
class Player
|
4
|
+
def initialize(json)
|
5
|
+
player_ivar = self.instance_variable_set("@player", {})
|
6
|
+
self.class.class_eval { attr_reader :"player" }
|
7
|
+
json.each_pair do | attr_name, attr_value|
|
8
|
+
player_ivar[attr_name.to_sym] = attr_value
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module SportsDataApi
|
2
|
+
module Ncaafb
|
3
|
+
class Season
|
4
|
+
attr_reader :year, :type, :weeks
|
5
|
+
|
6
|
+
def initialize(season_hash)
|
7
|
+
@weeks = []
|
8
|
+
@year = season_hash['season']
|
9
|
+
@type = season_hash['type'].to_sym
|
10
|
+
@weeks = season_hash['weeks'].map do |week_hash|
|
11
|
+
Week.new(@year, @type, week_hash)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
##
|
16
|
+
# Check if the requested season is a valid
|
17
|
+
# Ncaafb season type.
|
18
|
+
#
|
19
|
+
# The only valid types are: :REG
|
20
|
+
def self.valid?(season)
|
21
|
+
[:REG].include?(season)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module SportsDataApi
|
2
|
+
module Ncaafb
|
3
|
+
class Team
|
4
|
+
attr_reader :id, :name, :conference, :division, :market, :remaining_challenges,
|
5
|
+
:remaining_timeouts, :score, :quarters, :venue, :points, :statistics, :players, :subdivision
|
6
|
+
|
7
|
+
def initialize(team_hash, conference = nil, division = nil, subdivision = nil)
|
8
|
+
if team_hash
|
9
|
+
@id = team_hash['id']
|
10
|
+
@name = team_hash['name']
|
11
|
+
@conference = conference
|
12
|
+
@division = division
|
13
|
+
@subdivision = subdivision
|
14
|
+
@market = team_hash['market']
|
15
|
+
@remaining_challenges = team_hash['remaining_challenges']
|
16
|
+
@remaining_timeouts = team_hash['remaining_timeouts']
|
17
|
+
@quarters = []
|
18
|
+
if team_hash['scoring']
|
19
|
+
team_hash['scoring'].each do |scoring_hash|
|
20
|
+
@quarters[scoring_hash['quarter']-1] = scoring_hash['points']
|
21
|
+
end
|
22
|
+
end
|
23
|
+
@quarters = @quarters.fill(0, @quarters.size, 4 - @quarters.size)
|
24
|
+
|
25
|
+
# Parse the Venue data if it exists
|
26
|
+
if team_hash.key?('venue')
|
27
|
+
@venue = Venue.new(team_hash['venue'])
|
28
|
+
end
|
29
|
+
|
30
|
+
if team_hash['statistics']
|
31
|
+
@statistics = parse_team_statistics(team_hash['statistics'])
|
32
|
+
@players = parse_player_statistics(team_hash['statistics'])
|
33
|
+
end
|
34
|
+
@points = team_hash['points'] || score
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Sum the score of each quarter
|
39
|
+
def score
|
40
|
+
@quarters.inject(:+)
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Compare the Team with another team
|
45
|
+
def ==(other)
|
46
|
+
# Must have an id to compare
|
47
|
+
return false if self.id.nil?
|
48
|
+
|
49
|
+
if other.is_a? SportsDataApi::Ncaafb::Team
|
50
|
+
return false if other.id.nil?
|
51
|
+
self.id === other.id
|
52
|
+
elsif other.is_a? Symbol
|
53
|
+
self.id.to_sym === other
|
54
|
+
else
|
55
|
+
super(other)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def parse_team_statistics(stats_hash)
|
62
|
+
stats = {}
|
63
|
+
stats_hash.keys.each do |key|
|
64
|
+
stats[key] = stats_hash[key]['team']
|
65
|
+
end
|
66
|
+
stats
|
67
|
+
end
|
68
|
+
|
69
|
+
def parse_player_statistics(stats_hash)
|
70
|
+
players = []
|
71
|
+
stats_hash.keys.each do |key|
|
72
|
+
next if !stats_hash[key]['players']
|
73
|
+
stats_hash[key]['players'].each do |p|
|
74
|
+
player = players.select{|a| a['id'] == p['id']}.first || {}
|
75
|
+
players << player if !players.select{|a| a['id'] == p['id']}.first
|
76
|
+
['id', 'name', 'jersey', 'position'].each do |k|
|
77
|
+
player[k] = p.delete(k)
|
78
|
+
end
|
79
|
+
player[key] = p
|
80
|
+
end
|
81
|
+
end
|
82
|
+
players
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module SportsDataApi
|
2
|
+
module Ncaafb
|
3
|
+
class Teams
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
attr_reader :conferences, :subdivisions, :divisions
|
7
|
+
|
8
|
+
def initialize(teams_hash)
|
9
|
+
@teams = []
|
10
|
+
@allowed_divisions = []
|
11
|
+
@allowed_conferences = []
|
12
|
+
@allowed_subdivisions = []
|
13
|
+
did = teams_hash['id']
|
14
|
+
teams_hash['conferences'].each do |conference_hash|
|
15
|
+
# Conference ID. e.g ACC
|
16
|
+
cid = conference_hash['id']
|
17
|
+
@teams << Array(conference_hash['teams']).map { |team_hash| Team.new(team_hash, cid) }
|
18
|
+
|
19
|
+
# If Sub Division
|
20
|
+
Array(conference_hash['subdivisions']).each do |subdivision|
|
21
|
+
# SUB Division ID, e.g., ACC-ATLANTIC
|
22
|
+
sdid = subdivision['id']
|
23
|
+
@teams << subdivision['teams'].map { |team_hash| Team.new(team_hash, cid, did, sdid) }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
@teams.flatten!
|
28
|
+
|
29
|
+
uniq_conferences = @teams.map { |team| team.conference.upcase }.uniq
|
30
|
+
@allowed_conferences = concat_allowed_methods(uniq_conferences)
|
31
|
+
@conferences = uniq_conferences.map(&:to_sym)
|
32
|
+
|
33
|
+
@divisions = select_divisions_by_type(:division)
|
34
|
+
@subdivisions = select_divisions_by_type(:subdivision)
|
35
|
+
|
36
|
+
@allowed_divisions.concat(concat_allowed_methods(@divisions))
|
37
|
+
@allowed_subdivisions.concat(concat_allowed_methods(@subdivisions))
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
|
42
|
+
def [](search_index)
|
43
|
+
found_index = @teams.index(search_index)
|
44
|
+
unless found_index.nil?
|
45
|
+
@teams[found_index]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
#
|
51
|
+
def respond_to?(method)
|
52
|
+
@allowed_conferences.include?(method) || @allowed_divisions.include?(method) || @allowed_subdivisions
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
#
|
57
|
+
def method_missing(method, *args, &block)
|
58
|
+
case true
|
59
|
+
when @allowed_conferences.include?(method)
|
60
|
+
select_by(:conference, method)
|
61
|
+
when @allowed_divisions.include?(method)
|
62
|
+
select_by(:division, method)
|
63
|
+
when @allowed_subdivisions.include?(method)
|
64
|
+
select_by(:subdivision, method)
|
65
|
+
else
|
66
|
+
super(method, *args, &block)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
|
72
|
+
##
|
73
|
+
# Make the class Enumerable
|
74
|
+
def each(&block)
|
75
|
+
@teams.each do |team|
|
76
|
+
if block_given?
|
77
|
+
block.call team
|
78
|
+
else
|
79
|
+
yield team
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
private
|
84
|
+
|
85
|
+
def select_by(type, method)
|
86
|
+
normalize_method = method.downcase.to_sym
|
87
|
+
@teams.select do |team|
|
88
|
+
normalize_method == team.send(type).to_s.downcase.to_sym
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def select_divisions_by_type(division_type)
|
93
|
+
(@teams.map { |team| team.send(division_type).to_s.upcase.to_sym }.uniq - [:"", nil])
|
94
|
+
end
|
95
|
+
|
96
|
+
def concat_allowed_methods(list)
|
97
|
+
list.map { |str| str.to_sym }.concat(list.map { |str| str.downcase.to_sym })
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|