playwhe 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,148 +1,91 @@
1
- require 'date'
2
- require 'net/http'
3
-
4
- require 'playwhe/version'
1
+ require "date"
5
2
 
6
3
  module PlayWhe
7
- # The day that Play Whe had its first draw; its start date
8
- BIRTHDAY = Date.parse('4th July 1994')
4
+ # Play Whe's birthday, July 4th, 1994
5
+ BIRTHDAY = Date.new(1994, 7, 4)
6
+
7
+ # The date that Play Whe changed to having 3 draws per day
8
+ THREE_DRAWS_DATE = Date.new(2011, 11, 21) # November 21st, 2011
9
9
 
10
- # The day that Play Whe changed to having 3 draws per day
11
- THREE_DRAWS_DAY = Date.parse('21st November 2011')
10
+ # The date that Play Whe changed to having 4 draws per day
11
+ FOUR_DRAWS_DATE = Date.new(2015, 7, 6) # July 6th, 2015
12
12
 
13
- HOST = 'nlcb.co.tt'
14
- QUERY_PATH = 'search/pwq/countdateCash.php'
15
- QUERY_URL = URI("http://#{HOST}/#{QUERY_PATH}")
13
+ # The lowest Play Whe mark
14
+ LOWEST_MARK = 1
16
15
 
17
- # the lowest and highest Play Whe marks
18
- LOWEST_MARK = 1
16
+ # The highest Play Whe mark
19
17
  HIGHEST_MARK = 36
20
18
 
21
- # marks associated with their standard spirit
19
+ # The spirit name associated with each mark
22
20
  SPIRITS = {
23
- 1 => 'centipede',
24
- 2 => 'old lady',
25
- 3 => 'carriage',
26
- 4 => 'dead man',
27
- 5 => 'parson man',
28
- 6 => 'belly',
29
- 7 => 'hog',
30
- 8 => 'tiger',
31
- 9 => 'cattle',
32
- 10 => 'monkey',
33
- 11 => 'corbeau',
34
- 12 => 'king',
35
- 13 => 'crapaud',
36
- 14 => 'money',
37
- 15 => 'sick woman',
38
- 16 => 'jamette',
39
- 17 => 'pigeon',
40
- 18 => 'water boat',
41
- 19 => 'horse',
42
- 20 => 'dog',
43
- 21 => 'mouth',
44
- 22 => 'rat',
45
- 23 => 'house',
46
- 24 => 'queen',
47
- 25 => 'morocoy',
48
- 26 => 'fowl',
49
- 27 => 'little snake',
50
- 28 => 'red fish',
51
- 29 => 'opium man',
52
- 30 => 'house cat',
53
- 31 => 'parson wife',
54
- 32 => 'shrimps',
55
- 33 => 'spider',
56
- 34 => 'blind man',
57
- 35 => 'big snake',
58
- 36 => 'donkey'
21
+ 1 => "centipede",
22
+ 2 => "old lady",
23
+ 3 => "carriage",
24
+ 4 => "dead man",
25
+ 5 => "parson man",
26
+ 6 => "belly",
27
+ 7 => "hog",
28
+ 8 => "tiger",
29
+ 9 => "cattle",
30
+ 10 => "monkey",
31
+ 11 => "corbeau",
32
+ 12 => "king",
33
+ 13 => "crapaud",
34
+ 14 => "money",
35
+ 15 => "sick woman",
36
+ 16 => "jamette",
37
+ 17 => "pigeon",
38
+ 18 => "water boat",
39
+ 19 => "horse",
40
+ 20 => "dog",
41
+ 21 => "mouth",
42
+ 22 => "rat",
43
+ 23 => "house",
44
+ 24 => "queen",
45
+ 25 => "morocoy",
46
+ 26 => "fowl",
47
+ 27 => "little snake",
48
+ 28 => "red fish",
49
+ 29 => "opium man",
50
+ 30 => "house cat",
51
+ 31 => "parson wife",
52
+ 32 => "shrimps",
53
+ 33 => "spider",
54
+ 34 => "blind man",
55
+ 35 => "big snake",
56
+ 36 => "donkey"
59
57
  }
60
58
 
61
- def self.results_for_month(year, month)
62
- if date_in_range?(year, month)
63
- params = { 'year' => (year%100).to_s.rjust(2, '0'),
64
- 'month' => Date::ABBR_MONTHNAMES[month] }
65
-
66
- begin
67
- response = Net::HTTP.post_form(QUERY_URL, params)
68
- rescue
69
- raise ServiceUnavailable
70
- end
71
-
72
- case response
73
- when Net::HTTPOK
74
- return parse_body(year, params['year'], month, params['month'], response.body)
75
- else
76
- raise ServiceUnavailable
77
- end
78
- else
79
- # obviously we won't have any results
80
- return []
81
- end
82
- end
83
-
84
- def self.results_for_year(year, callbacks = {})
85
- all_results = []
86
-
87
- 12.times do |m|
88
- month = m + 1
89
-
90
- skip = callbacks[:before] ? callbacks[:before].call(month) : false
59
+ # The short-hand names for the time of day Play Whe results are drawn,
60
+ # ordered by earliest to latest
61
+ PERIODS = %w(EM AM AN PM)
62
+
63
+ # The times of day, represented as seconds past midnight, that Play Whe
64
+ # results are drawn
65
+ DRAW_TIMES = {
66
+ "EM" => 10 * 3600, # 10:00 AM
67
+ "AM" => 13 * 3600, # 1:00 PM
68
+ "AN" => 16 * 3600, # 4:00 PM
69
+ "PM" => 18 * 3600 + 1800 # 6:30 PM
70
+ }
91
71
 
92
- unless skip
93
- begin
94
- flag = :ok
95
- results = results_for_month(year, month)
96
- rescue ServiceUnavailable
97
- flag = :error
98
- results = []
99
- ensure
100
- begin
101
- continue = callbacks[:after] ? callbacks[:after].call(month, flag, results) : true
102
- rescue
103
- raise
104
- ensure
105
- all_results += results if results
106
- end
107
- end
72
+ class Error < StandardError; end
73
+ class NetworkError < Error; end
74
+ class BadResponseError < NetworkError
75
+ attr_reader :response
108
76
 
109
- break unless continue
110
- end
77
+ def initialize(response)
78
+ @response = response
79
+ super("status: #{response.status}, body: #{response.body}")
111
80
  end
112
-
113
- all_results
114
- end
115
-
116
- class ServiceUnavailable < Exception
117
- end
118
-
119
- private
120
-
121
- def self.date_in_range?(year, month)
122
- mday = year == BIRTHDAY.year && month == BIRTHDAY.month ? BIRTHDAY.day : 1
123
- date = Date.new(year, month, mday)
124
-
125
- date >= BIRTHDAY && date <= Date.today
126
- end
127
-
128
- def self.parse_body(year, yy, month, abbr_month, body)
129
- # Here's the current form of the data on the page as of 27/05/2012:
130
- #
131
- # <date>: Draw # <draw> : <period>'s draw was <mark>
132
- #
133
- # where
134
- #
135
- # <date> : dd-Mmm-yy
136
- # <draw> : a positive integer
137
- # <period>: Morning | Midday | Evening
138
- # <mark> : 1..36
139
- results = body.scan(/(\d{2})-#{abbr_month}-#{yy}: Draw # (\d+) : (Morning|Midday|Evening)'s draw was (\d+)/)
140
-
141
- results.collect! do |r|
142
- { draw: r[1].to_i,
143
- date: Date.new(year, month, r[0].to_i),
144
- period: {'Morning' => 1, 'Midday' => 2, 'Evening' => 3}[r[2]],
145
- mark: r[3].to_i }
146
- end.sort_by! { |r| r[:draw] }
147
81
  end
148
82
  end
83
+
84
+ require_relative "playwhe/version"
85
+ require_relative "playwhe/settings"
86
+ require_relative "playwhe/util"
87
+ require_relative "playwhe/http"
88
+ require_relative "playwhe/fetcher"
89
+ require_relative "playwhe/result"
90
+ require_relative "playwhe/parser"
91
+ require_relative "playwhe/get"
@@ -0,0 +1,25 @@
1
+ module PlayWhe
2
+ class Fetcher
3
+ include Util
4
+
5
+ attr_reader :http_adapter, :url
6
+
7
+ def initialize(http_adapter, url)
8
+ @http_adapter = http_adapter
9
+ @url = url
10
+ end
11
+
12
+ def get(year:, month: nil)
13
+ y = normalize_year(year)
14
+ m = normalize_month(month)
15
+
16
+ response = http_adapter.post(url, year: y, month: m)
17
+
18
+ if response.ok?
19
+ response.body
20
+ else
21
+ raise BadResponseError, response
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,214 @@
1
+ require "date"
2
+ require "ostruct"
3
+
4
+ require "http"
5
+
6
+ module PlayWhe
7
+ class << self
8
+ def get
9
+ @_get ||= Get.new(fetcher: fetcher, parser: Parser)
10
+ end
11
+
12
+ private
13
+
14
+ def fetcher
15
+ settings = Settings.config
16
+
17
+ http = ::HTTP.timeout :global,
18
+ read: settings.http.read_timeout,
19
+ write: settings.http.write_timeout,
20
+ connect: settings.http.connect_timeout
21
+
22
+ Fetcher.new(HTTP::Adapter.new(http), settings.url)
23
+ end
24
+ end
25
+
26
+ class Get
27
+ attr_reader :fetcher, :parser
28
+
29
+ def initialize(fetcher:, parser:)
30
+ @fetcher = fetcher
31
+ @parser = parser
32
+ end
33
+
34
+ VALID_ORDERS = %i(asc desc)
35
+
36
+ def results_for_year(date, order: :asc, skip_validation: false)
37
+ unless !!skip_validation
38
+ date = clean_and_validate_date(date)
39
+ order = clean_and_validate_order(order)
40
+ end
41
+
42
+ sort \
43
+ partition(
44
+ parse(fetcher.get(year: date.year)),
45
+ validation_context(date.year)
46
+ ),
47
+ order
48
+ end
49
+
50
+ def results_for_month(date, order: :asc, skip_validation: false)
51
+ unless !!skip_validation
52
+ date = clean_and_validate_date(date)
53
+ order = clean_and_validate_order(order)
54
+ end
55
+
56
+ sort \
57
+ partition(
58
+ parse(fetcher.get(year: date.year, month: date.month)),
59
+ validation_context(date.year, date.month)
60
+ ),
61
+ order
62
+ end
63
+
64
+ def results_for_day(date, order: :asc, skip_validation: false)
65
+ unless !!skip_validation
66
+ date = clean_and_validate_date(date)
67
+ order = clean_and_validate_order(order)
68
+ end
69
+
70
+ sort \
71
+ partition(
72
+ parse(fetcher.get(year: date.year, month: date.month)).select { |r|
73
+ r.date == date
74
+ }
75
+ ),
76
+ order
77
+ end
78
+
79
+ def most_recent(limit: nil, order: :desc, skip_validation: false)
80
+ unless !!skip_validation
81
+ limit = clean_and_validate_limit(limit)
82
+ order = clean_and_validate_order(order)
83
+ end
84
+
85
+ start_year = Date.today.year
86
+ end_year = BIRTHDAY.year
87
+ delta = -1
88
+
89
+ results_for_range(start_year, end_year, delta, limit, order)
90
+ end
91
+
92
+ def least_recent(limit: nil, order: :asc, skip_validation: false)
93
+ unless !!skip_validation
94
+ limit = clean_and_validate_limit(limit)
95
+ order = clean_and_validate_order(order)
96
+ end
97
+
98
+ start_year = BIRTHDAY.year
99
+ end_year = Date.today.year
100
+ delta = 1
101
+
102
+ results_for_range(start_year, end_year, delta, limit, order)
103
+ end
104
+
105
+ private
106
+
107
+ def results_for_range(start_year, end_year, delta, limit, order)
108
+ if limit.zero?
109
+ sort \
110
+ partition(
111
+ parse(fetcher.get(year: start_year)),
112
+ validation_context(start_year)
113
+ ),
114
+ order
115
+ else
116
+ year = start_year
117
+ all = []
118
+
119
+ while all.length < limit
120
+ all.concat(parse(fetcher.get(year: year)))
121
+ break if year == end_year
122
+ year += delta
123
+ end
124
+
125
+ all = sort_desc(all) if start_year > end_year
126
+ all = sort_asc(all) if start_year < end_year
127
+
128
+ sort \
129
+ partition(all[0, limit]),
130
+ order
131
+ end
132
+ end
133
+
134
+ def sort(partitioned_results, order)
135
+ [
136
+ send(:"sort_#{order}", partitioned_results.first),
137
+ partitioned_results.last
138
+ ]
139
+ end
140
+
141
+ def sort_asc(results)
142
+ default_date = Date.today
143
+ default_period_index = PERIODS.length
144
+ results.sort_by do |r|
145
+ [
146
+ r.date || default_date,
147
+ PERIODS.index(r.period) || default_period_index
148
+ ]
149
+ end
150
+ end
151
+
152
+ def sort_desc(results)
153
+ sort_asc(results).reverse
154
+ end
155
+
156
+ def partition(results, context = nil)
157
+ results.partition { |r| r.is_valid?(context) }
158
+ end
159
+
160
+ def parse(html_results)
161
+ parser.parse(html_results)
162
+ end
163
+
164
+ def validation_context(year, month = nil)
165
+ OpenStruct.new(year: year, month: month)
166
+ end
167
+
168
+ def clean_and_validate_date(date)
169
+ if date.nil?
170
+ raise ArgumentError, "date is required"
171
+ else
172
+ unless date.instance_of?(Date)
173
+ begin
174
+ date = Date.parse(date.to_s)
175
+ rescue ArgumentError
176
+ raise ArgumentError, "date must be a date"
177
+ end
178
+ end
179
+
180
+ if date < BIRTHDAY || date > Date.today
181
+ raise ArgumentError, "date must be between " \
182
+ "#{format_date(BIRTHDAY)} and " \
183
+ "#{format_date(Date.today)} inclusive"
184
+ end
185
+ end
186
+
187
+ date
188
+ end
189
+
190
+ def clean_and_validate_order(order)
191
+ order = order.to_s.to_sym
192
+
193
+ unless VALID_ORDERS.include?(order)
194
+ raise ArgumentError, "order must be one of #{VALID_ORDERS.join(', ')}"
195
+ end
196
+
197
+ order
198
+ end
199
+
200
+ def clean_and_validate_limit(limit)
201
+ limit = limit.to_i
202
+
203
+ if limit < 0
204
+ raise ArgumentError, "limit must be a non-negative integer"
205
+ end
206
+
207
+ limit
208
+ end
209
+
210
+ def format_date(date)
211
+ date.strftime("%Y-%m-%d")
212
+ end
213
+ end
214
+ end