sportdb-parser 0.7.1 → 0.7.2

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -1
  3. data/Manifest.txt +17 -4
  4. data/lib/sportdb/parser/lexer-on_goal.rb +172 -0
  5. data/lib/sportdb/parser/lexer-on_group_def.rb +31 -0
  6. data/lib/sportdb/parser/lexer-on_prop_lineup.rb +79 -0
  7. data/lib/sportdb/parser/lexer-on_prop_misc.rb +110 -0
  8. data/lib/sportdb/parser/lexer-on_prop_penalties.rb +40 -0
  9. data/lib/sportdb/parser/lexer-on_round_def.rb +37 -0
  10. data/lib/sportdb/parser/lexer-on_top.rb +125 -0
  11. data/lib/sportdb/parser/lexer-prep_doc.rb +131 -0
  12. data/lib/sportdb/parser/lexer-prep_line.rb +63 -0
  13. data/lib/sportdb/parser/lexer-tokenize.rb +449 -0
  14. data/lib/sportdb/parser/lexer.rb +133 -1363
  15. data/lib/sportdb/parser/lexer_buffer.rb +8 -37
  16. data/lib/sportdb/parser/lexer_token.rb +126 -0
  17. data/lib/sportdb/parser/parser.rb +1104 -1403
  18. data/lib/sportdb/parser/racc_parser.rb +36 -32
  19. data/lib/sportdb/parser/racc_tree.rb +65 -98
  20. data/lib/sportdb/parser/token-date--helpers.rb +130 -0
  21. data/lib/sportdb/parser/token-date--names.rb +108 -0
  22. data/lib/sportdb/parser/token-date.rb +20 -192
  23. data/lib/sportdb/parser/token-date_duration.rb +8 -27
  24. data/lib/sportdb/parser/token-geo.rb +16 -16
  25. data/lib/sportdb/parser/token-goals--helpers.rb +114 -0
  26. data/lib/sportdb/parser/token-goals.rb +103 -249
  27. data/lib/sportdb/parser/token-group.rb +8 -22
  28. data/lib/sportdb/parser/token-prop.rb +138 -124
  29. data/lib/sportdb/parser/token-prop_name.rb +48 -39
  30. data/lib/sportdb/parser/token-round.rb +21 -35
  31. data/lib/sportdb/parser/token-score--helpers.rb +189 -0
  32. data/lib/sportdb/parser/token-score.rb +9 -393
  33. data/lib/sportdb/parser/token-score_full.rb +331 -0
  34. data/lib/sportdb/parser/token-status.rb +44 -46
  35. data/lib/sportdb/parser/token-status_inline.rb +112 -0
  36. data/lib/sportdb/parser/token-text.rb +41 -31
  37. data/lib/sportdb/parser/token-time.rb +29 -26
  38. data/lib/sportdb/parser/token.rb +58 -159
  39. data/lib/sportdb/parser/version.rb +1 -1
  40. data/lib/sportdb/parser.rb +45 -17
  41. metadata +19 -6
  42. data/lib/sportdb/parser/blocktxt.rb +0 -99
  43. data/lib/sportdb/parser/lexer_tty.rb +0 -111
  44. data/lib/sportdb/parser/token-table.rb +0 -149
  45. data/lib/sportdb/parser/token_helpers.rb +0 -92
@@ -5,61 +5,70 @@
5
5
  class RaccMatchParser
6
6
 
7
7
 
8
- def initialize( txt, debug: false )
9
- ## puts "==> txt:"
10
- ## puts txt
11
-
12
- @tree = []
8
+
9
+ def initialize( txt, debug: false )
10
+ @tree = []
13
11
  @errors = []
14
12
 
15
- ### todo:
16
- ## - pass along debug flag
17
13
  lexer = SportDb::Lexer.new( txt, debug: debug )
18
14
  ## note - use tokenize_with_errors and add/collect tokenize errors
19
15
  @tokens, @errors = lexer.tokenize_with_errors
20
16
  ## pp @tokens
21
-
22
- ## quick hack - convert to racc format single char literal tokens e.g. '@' etc.
23
- @tokens = @tokens.map do |tok|
24
- if tok.size == 1
25
- [tok[0].to_s, tok[0].to_s]
26
- else
27
- tok
28
- end
29
- end
17
+
30
18
  end
31
19
 
32
20
 
33
21
  def debug( value ) @debug = value; end ### fix: use setter-style e.g. debug=(value) !!!
34
22
  def debug?() @debug == true; end
35
23
 
24
+ ###
25
+ ### fix-fix-fix rename to _trace
26
+ ## check lexer - add [debug] Parser - or such!!!
27
+ ##
36
28
  ## debug - trace / print message
37
29
  def trace( msg )
38
- puts " [parse] " + msg if debug?
30
+ puts " fix-fix-fix use _trace() in parser!!"
31
+ puts " [parse] " + msg
39
32
  end
40
33
 
41
-
34
+ def _trace( *args )
35
+ ## if debug?
36
+ print "[DEBUG] Parser -- "
37
+ args.each { |arg| puts args }
38
+ ## end
39
+ end
42
40
 
43
41
 
44
42
  def next_token
45
43
  tok = @tokens.shift
46
- trace( "next_token => #{tok.pretty_inspect}" )
44
+ _trace( "next_token => #{tok.pretty_inspect}" )
45
+
46
+ ## convert to racc format single char literal tokens e.g. '@' etc.
47
+ ## note - literal token MUST be string (NOT symbol)
48
+ ## note - racc expects array with to items
49
+ ## - item[0] is the token id
50
+ ## - item[1] is the token value
51
+
52
+ ## note - returns nil for end-of-file !!!
53
+ tok = [tok.type, tok] if tok
54
+
47
55
  tok
48
56
  end
49
-
57
+
58
+
50
59
  # on_error do |error_token_id, error_value, value_stack|
51
60
  # puts "Parse error on token: #{error_token_id}, value: #{error_value}"
52
- # end
61
+ # end
53
62
 
54
- def parse_with_errors
55
- trace( "start parse:" )
63
+ def parse_with_errors
64
+ _trace( "start parse:" )
56
65
  do_parse
57
66
  [@tree, @errors]
58
67
  end
59
68
 
60
69
  def parse ## convenience shortcut (ignores errors)
61
70
  tree, _ = parse_with_errors
62
- tree
71
+ tree
63
72
  end
64
73
 
65
74
 
@@ -69,20 +78,15 @@ def initialize( txt, debug: false )
69
78
 
70
79
  def on_error(error_token_id, error_value, value_stack)
71
80
  ## auto-add error_token (as string)
72
- error_token = Racc_token_to_s_table[error_token_id]
81
+ error_token = Racc_token_to_s_table[error_token_id]
73
82
  args = [error_token, error_token_id, error_value, value_stack]
83
+
74
84
  puts
75
85
  puts "!! on parse error:"
76
86
  puts "args=#{args.pretty_inspect}"
77
87
 
78
- @errors << "parse error on token: #{error_token} (#{error_token_id}) with value: #{error_value}, stack: #{value_stack.pretty_inspect}"
79
- ## exit 1 ## exit for now - get and print more info about context etc.!!
88
+ @errors << "parse error on token: #{error_token} >#{error_value.pretty_inspect}<, stack: #{value_stack.pretty_inspect}"
80
89
  end
81
90
 
82
91
 
83
- =begin
84
- on_error do |error_token_id, error_value, value_stack|
85
- puts "Parse error on token: #{error_token_id}, value: #{error_value}"
86
- end
87
- =end
88
92
  end # class RaccMatchParser
@@ -5,7 +5,7 @@
5
5
  class RaccMatchParser
6
6
 
7
7
  =begin
8
- RefereeLine = Struct.new( :name, :country ) do
8
+ RefereeLine = Struct.new( :name, :country ) do
9
9
  def pretty_print( printer )
10
10
  printer.text( "<RefereeLine " )
11
11
  printer.text( self.name )
@@ -16,7 +16,7 @@ end
16
16
  =end
17
17
 
18
18
  ## support multiple referees (incl. assistant refs etc.)
19
- RefereeLine = Struct.new( :referees ) do
19
+ RefereeLine = Struct.new( :referees ) do
20
20
  def pretty_print( printer )
21
21
  printer.text( "<RefereeLine " )
22
22
  printer.text( self.referees.pretty_inspect )
@@ -28,16 +28,16 @@ Referee = Struct.new( :name, :country ) do
28
28
  def to_s
29
29
  buf = String.new
30
30
  buf << self.name
31
- buf << " (#{self.country})" if self.country
31
+ buf << " (#{self.country})" if self.country
32
32
  buf
33
33
  end
34
34
 
35
35
  def pretty_print( printer )
36
36
  printer.text( to_s )
37
- end
37
+ end
38
38
  end
39
39
 
40
- AttendanceLine = Struct.new( :att ) do
40
+ AttendanceLine = Struct.new( :att ) do
41
41
  def pretty_print( printer )
42
42
  printer.text( "<AttendanceLine #{self.att}>" )
43
43
  end
@@ -45,7 +45,7 @@ end
45
45
 
46
46
 
47
47
 
48
- PenaltiesLine = Struct.new( :penalties ) do
48
+ PenaltiesLine = Struct.new( :penalties ) do
49
49
  def pretty_print( printer )
50
50
  printer.text( "<PenaltiesLine " )
51
51
  printer.text( self.penalties.pretty_inspect )
@@ -59,21 +59,21 @@ Penalty = Struct.new( :name, :score, :note ) do
59
59
  buf = String.new
60
60
  buf << "#{self.score[0]}-#{self.score[1]} " if self.score
61
61
  buf << self.name
62
- buf << " (#{self.note})" if self.note
62
+ buf << " (#{self.note})" if self.note
63
63
  buf
64
64
  end
65
65
 
66
66
 
67
67
  def pretty_print( printer )
68
68
  printer.text( to_s )
69
- end
69
+ end
70
70
  end
71
71
 
72
72
 
73
73
 
74
74
 
75
75
  ## find a better name for player (use bookings?) - note - red/yellow card for trainer possible
76
- CardsLine = Struct.new( :type, :bookings ) do
76
+ CardsLine = Struct.new( :type, :bookings ) do
77
77
  def pretty_print( printer )
78
78
  printer.text( "<CardsLine " )
79
79
  printer.text( self.type )
@@ -92,7 +92,7 @@ Booking = Struct.new( :name, :minute ) do
92
92
 
93
93
  def pretty_print( printer )
94
94
  printer.text( to_s )
95
- end
95
+ end
96
96
  end
97
97
 
98
98
 
@@ -111,11 +111,11 @@ end
111
111
  Lineup = Struct.new( :name, :captain, :cards, :sub ) do
112
112
  def pretty_print( printer )
113
113
  buf = String.new
114
- buf << self.name
114
+ buf << self.name
115
115
  buf << " [c]" if captain
116
116
  buf << " cards=" + self.cards.pretty_inspect if cards
117
117
  buf << " sub=" + self.sub.pretty_inspect if sub
118
- printer.text( buf )
118
+ printer.text( buf )
119
119
  end
120
120
  end
121
121
 
@@ -130,18 +130,18 @@ Card = Struct.new( :name, :minute ) do
130
130
 
131
131
  def pretty_print( printer )
132
132
  printer.text( to_s )
133
- end
133
+ end
134
134
  end
135
135
 
136
136
 
137
137
  Sub = Struct.new( :minute, :sub ) do
138
138
  def pretty_print( printer )
139
- buf = String.new
139
+ buf = String.new
140
140
  buf << "(" ## note - possibly recursive (thus, let minute go first/print first/upfront)
141
- buf << "#{self.minute.to_s} " if self.minute
142
- buf << "#{self.sub.pretty_inspect}"
141
+ buf << "#{self.minute.to_s} " if self.minute
142
+ buf << "#{self.sub.pretty_inspect}"
143
143
  buf << ")"
144
- printer.text( buf )
144
+ printer.text( buf )
145
145
  end
146
146
  end
147
147
 
@@ -201,13 +201,6 @@ class BlankLine
201
201
  end
202
202
  end
203
203
 
204
- ## todo/check - find a better name for hruler - divider? or ??? - why? why not?
205
- class HRuler ## h(orizontal) ruler (for breaks; new scopes)
206
- def pretty_print( printer )
207
- printer.text( "<HRuler>" )
208
- end
209
- end
210
-
211
204
 
212
205
 
213
206
  NoteLine = Struct.new( :text ) do
@@ -223,32 +216,6 @@ NotaBene = Struct.new( :text ) do
223
216
  end
224
217
 
225
218
 
226
- ## todo/check - rename TableHeading to TableHeader - why? why not?
227
- TableHeading = Struct.new( :text ) do
228
- def pretty_print( printer )
229
- printer.text( "<TableHeading #{self.text}>" )
230
- end
231
- end
232
-
233
- ## todo/check - rename TableDivider to TableRule/TableRuler/TableLine - why? why not?
234
- TableDivider = Struct.new( :text ) do
235
- def pretty_print( printer )
236
- printer.text( "<TableDivider #{self.text}>" )
237
- end
238
- end
239
-
240
- TableLine = Struct.new( :text ) do
241
- def pretty_print( printer )
242
- printer.text( "<TableLine #{self.text}>" )
243
- end
244
- end
245
-
246
- TableNote = Struct.new( :text ) do
247
- def pretty_print( printer )
248
- printer.text( "<TableNote #{self.text}>" )
249
- end
250
- end
251
-
252
219
 
253
220
 
254
221
  ## todo/check - use a generic Heading instead of Heading1/2/3 - why? why not?
@@ -278,7 +245,7 @@ MatchLineBye = Struct.new( :team, :note ) do
278
245
  printer.text( "#{self.team} bye")
279
246
  printer.text( " note=#{self.note.pretty_inspect}" ) if self.note
280
247
  printer.text( ">" )
281
- end
248
+ end
282
249
  end
283
250
 
284
251
  MatchLineWalkover = Struct.new( :team1, :team2, :note ) do
@@ -287,10 +254,10 @@ MatchLineWalkover = Struct.new( :team1, :team2, :note ) do
287
254
  printer.text( "#{self.team1} w/o #{self.team2}")
288
255
  printer.text( " note=#{self.note.pretty_inspect}" ) if self.note
289
256
  printer.text( ">" )
290
- end
257
+ end
291
258
  end
292
259
 
293
- MatchLineLegs = Struct.new( :team1, :team2,
260
+ MatchLineLegs = Struct.new( :team1, :team2,
294
261
  :score ) do ## change to geos - why? why not?
295
262
  def pretty_print( printer )
296
263
  printer.text( "<MatchLineLegs " )
@@ -300,26 +267,26 @@ MatchLineLegs = Struct.new( :team1, :team2,
300
267
  members.zip(values) do |name, value|
301
268
  next if [:team1, :team2].include?( name )
302
269
  next if value.nil?
303
-
270
+
304
271
  printer.text( "#{name}=#{value.pretty_inspect}" )
305
- end
272
+ end
306
273
 
307
274
  printer.text( ">" )
308
- end
275
+ end
309
276
  end
310
277
 
311
278
 
312
279
  #
313
280
  # note: use two status attributes for now
314
281
  # 1) inline_status and 2) (note_)status
315
- # for now e.g. A abd. B vs A v B [abadoned]
282
+ # for now e.g. A abd. B vs A v B [abadoned]
316
283
  # A 3-0 awd B vs A 3-0 B [awarded]
317
284
  # note - BOTH might be present at the same time
318
285
 
319
286
 
320
287
  MatchLine = Struct.new( :header, :tty, ## tty = TELETYPE MODE for teams and score!!
321
288
  :num, :date, :time, :time_local, :year,
322
- :team1, :team2,
289
+ :team1, :team2,
323
290
  :score,
324
291
  :status, :status_inline, :status_note,
325
292
  :geo,
@@ -335,12 +302,12 @@ MatchLine = Struct.new( :header, :tty, ## tty = TELETYPE MODE for teams and
335
302
  members.zip(values) do |name, value|
336
303
  next if [:team1, :team2].include?( name )
337
304
  next if value.nil?
338
-
305
+
339
306
  printer.text( "#{name}=#{value.pretty_inspect}" )
340
- end
307
+ end
341
308
 
342
309
  printer.text( ">" )
343
- end
310
+ end
344
311
 
345
312
  end
346
313
 
@@ -351,12 +318,12 @@ GoalLine = Struct.new( :goals1, :goals2 ) do
351
318
  printer.text( "goals1=" + self.goals1.pretty_inspect + "," )
352
319
  printer.breakable
353
320
  printer.text( "goals2=" + self.goals2.pretty_inspect + ">" )
354
- end
321
+ end
355
322
 
356
323
  ## def clone
357
- ## _clone = GoalLine.new( goals1: goals1.clone,
324
+ ## _clone = GoalLine.new( goals1: goals1.clone,
358
325
  ## goals2: goals2.clone )
359
- ##
326
+ ##
360
327
  ## puts "[debug] clone #{self.pretty_inspect} => #{_clone.pretty_inspect}"
361
328
  ##
362
329
  ## _clone
@@ -386,7 +353,7 @@ Goal = Struct.new( :player, :minutes, :count ) do
386
353
 
387
354
  def pretty_print( printer )
388
355
  printer.text( to_s )
389
- end
356
+ end
390
357
 
391
358
  ## def clone
392
359
  ## puts "[debug] clone #{self.pretty_inspect}"
@@ -402,7 +369,7 @@ GoalLineAlt = Struct.new( :goals ) do
402
369
  def pretty_print( printer )
403
370
  printer.text( "<GoalLineAlt " )
404
371
  printer.text( "goals=" + self.goals.pretty_inspect + ">" )
405
- end
372
+ end
406
373
  end
407
374
 
408
375
 
@@ -423,7 +390,7 @@ GoalAlt = Struct.new( :score, :player, :minute, :goal_type ) do
423
390
 
424
391
  def pretty_print( printer )
425
392
  printer.text( to_s )
426
- end
393
+ end
427
394
  end
428
395
 
429
396
 
@@ -432,7 +399,7 @@ GoalLineCompat = Struct.new( :goals ) do
432
399
  def pretty_print( printer )
433
400
  printer.text( "<GoalLineCompat " )
434
401
  printer.text( "goals=" + self.goals.pretty_inspect + ">" )
435
- end
402
+ end
436
403
  end
437
404
 
438
405
  ##
@@ -443,7 +410,7 @@ end
443
410
  GoalCompat = Struct.new( :score, :player, :minute, :goal_type ) do
444
411
  def to_s
445
412
  buf = String.new
446
- buf << "#{self.minute}"
413
+ buf << "#{self.minute}"
447
414
  buf << " #{self.player}"
448
415
  buf << " #{self.goal_type}" if self.goal_type
449
416
  buf << " #{self.score[0]}-#{self.score[1]}" if self.score
@@ -452,7 +419,7 @@ GoalCompat = Struct.new( :score, :player, :minute, :goal_type ) do
452
419
 
453
420
  def pretty_print( printer )
454
421
  printer.text( to_s )
455
- end
422
+ end
456
423
  end
457
424
 
458
425
 
@@ -477,31 +444,31 @@ GoalMinute = Struct.new( :m, :offset, :secs,
477
444
  buf = String.new
478
445
  buf << "#{self.m}"
479
446
  buf << "'"
480
- buf << "+#{self.offset}" if self.offset
481
- buf << " (og)" if self.og
482
- buf << " (pen)" if self.pen
483
- buf << " (f)" if self.freekick
484
- buf << " (h)" if self.header
447
+ buf << "+#{self.offset}" if self.offset
448
+ buf << "(og)" if self.og
449
+ buf << "(p)" if self.pen
450
+ buf << "(f)" if self.freekick
451
+ buf << "(h)" if self.header
485
452
  buf << " (#{self.secs} secs)" if self.secs
486
453
  buf
487
454
  end
488
-
489
- def pretty_print( printer )
490
- printer.text( to_s )
491
- end
455
+
456
+ def pretty_print( printer )
457
+ printer.text( to_s )
458
+ end
492
459
 
493
460
  ### quick hack:
494
461
  ### split struct into Minute+GoalType structs
495
462
  def to_minute
496
- Minute.new( m: self.m,
463
+ Minute.new( m: self.m,
497
464
  offset: self.offset,
498
465
  secs: self.secs )
499
466
  end
500
467
 
501
468
  def to_goal_type
502
- if self.og || self.pen || self.header || self.freekick
503
- GoalType.new( og: self.og,
504
- pen: self.pen,
469
+ if self.og || self.pen || self.header || self.freekick
470
+ GoalType.new( og: self.og,
471
+ pen: self.pen,
505
472
  header: self.header,
506
473
  freekick: self.freekick )
507
474
  else
@@ -511,14 +478,14 @@ GoalMinute = Struct.new( :m, :offset, :secs,
511
478
 
512
479
  ## def clone
513
480
  ## puts "[debug] clone #{self.pretty_inspect}"
514
- ## GoalMinute.new( m: m.clone,
515
- ## offset: offset.clone,
481
+ ## GoalMinute.new( m: m.clone,
482
+ ## offset: offset.clone,
516
483
  ## secs: secs.clone,
517
- ## og: og.clone,
518
- ## pen: pen.clone,
519
- ## header: header.clone,
484
+ ## og: og.clone,
485
+ ## pen: pen.clone,
486
+ ## header: header.clone,
520
487
  ## freekick: freekick.clone )
521
- ##
488
+ ##
522
489
  ## end
523
490
  end
524
491
 
@@ -532,10 +499,10 @@ GoalType = Struct.new( :og, :pen, :header, :freekick ) do
532
499
  buf << "(h)" if self.header
533
500
  buf
534
501
  end
535
-
536
- def pretty_print( printer )
537
- printer.text( to_s )
538
- end
502
+
503
+ def pretty_print( printer )
504
+ printer.text( to_s )
505
+ end
539
506
  end
540
507
 
541
508
 
@@ -544,14 +511,14 @@ Minute = Struct.new( :m, :offset, :secs ) do
544
511
  buf = String.new
545
512
  buf << "#{self.m}"
546
513
  buf << "'"
547
- buf << "+#{self.offset}" if self.offset
514
+ buf << "+#{self.offset}" if self.offset
548
515
  buf << "/#{self.secs} secs" if self.secs
549
516
  buf
550
517
  end
551
-
552
- def pretty_print( printer )
553
- printer.text( to_s )
554
- end
518
+
519
+ def pretty_print( printer )
520
+ printer.text( to_s )
521
+ end
555
522
  end
556
523
 
557
524
  end # class RaccMatchParser
@@ -0,0 +1,130 @@
1
+ module SportDb
2
+ class Lexer
3
+
4
+
5
+ ## "internal" date helpers
6
+ def self._build_date( m )
7
+ date = {}
8
+ ## map month names
9
+ ## note - allow any/upcase JULY/JUL etc. thus ALWAYS downcase for lookup
10
+ date[:y] = m[:year].to_i(10) if m[:year]
11
+ ## check - use y too for two-digit year or keep separate - why? why not?
12
+ date[:yy] = m[:yy].to_i(10) if m[:yy] ## two digit year (e.g. 25 or 78 etc.)
13
+ date[:m] = m[:month].to_i(10) if m[:month]
14
+ date[:m] = MONTH_MAP[ m[:month_name].downcase ] if m[:month_name]
15
+ date[:d] = m[:day].to_i(10) if m[:day]
16
+ date[:wday] = DAY_MAP[ m[:day_name].downcase ] if m[:day_name]
17
+
18
+ date
19
+ end
20
+
21
+ def self._build_date_legs( m )
22
+ legs = {}
23
+ ## map month names
24
+ ## note - allow any/upcase JULY/JUL etc. thus ALWAYS downcase for lookup
25
+ date = {}
26
+ date[:m] = MONTH_MAP[ m[:month_name1].downcase ]
27
+ date[:d] = m[:day1].to_i(10)
28
+ legs[:date1] = date
29
+
30
+ date = {}
31
+ date[:m] = MONTH_MAP[ m[:month_name2].downcase ] if m[:month_name2]
32
+ date[:d] = m[:day2].to_i(10)
33
+ legs[:date2] = date
34
+
35
+ legs
36
+ end
37
+
38
+
39
+ def self._build_duration( m )
40
+ ## todo/check/fix - if end: works for kwargs!!!!!
41
+ duration = { start: {}, end: {}}
42
+
43
+ duration[:start][:y] = m[:year1].to_i(10) if m[:year1]
44
+ duration[:start][:m] = MONTH_MAP[ m[:month_name1].downcase ] if m[:month_name1]
45
+ duration[:start][:d] = m[:day1].to_i(10) if m[:day1]
46
+ duration[:start][:wday] = DAY_MAP[ m[:day_name1].downcase ] if m[:day_name1]
47
+
48
+ duration[:end][:y] = m[:year2].to_i(10) if m[:year2]
49
+ duration[:end][:m] = MONTH_MAP[ m[:month_name2].downcase ] if m[:month_name2]
50
+ duration[:end][:d] = m[:day2].to_i(10) if m[:day2]
51
+ duration[:end][:wday] = DAY_MAP[ m[:day_name2].downcase ] if m[:day_name2]
52
+
53
+ duration
54
+ end
55
+
56
+
57
+
58
+
59
+ def _build_date( m ) self.class._build_date( m ); end
60
+ def _build_date_legs( m ) self.class._build_date_legs( m ); end
61
+ def _build_duration( m ) self.class._build_duration( m ); end
62
+
63
+
64
+
65
+
66
+ #############
67
+ ## "top-level" add a date parser helper
68
+
69
+ ## note: parse_date - returns Date object
70
+ ## _parse_date (with underscore) - return hash of "parsed" regex match data!!
71
+
72
+ def self.parse_date( str, start: nil )
73
+ if m = _parse_date( str )
74
+ year = m[:y]
75
+ yy = m[:yy]
76
+
77
+ ####
78
+ ## support two digit shortcut for year
79
+ if yy && year.nil?
80
+ ###
81
+ ## for now assume 00,01 to 30 is 2000,2001 to 2030
82
+ ## and 31 to 99 is 1931 to 1999
83
+ year = yy <= 30 ? 2000+yy : 1900+yy
84
+ end
85
+
86
+ month = m[:m]
87
+ day = m[:d]
88
+ wday = m[:wday]
89
+
90
+
91
+ if year.nil? ## try to calculate year
92
+ raise ArgumentError, "year required in date >#{str}< or pass along start date" if start.nil?
93
+
94
+ year = if month > start.month ||
95
+ (month == start.month && day >= start.day)
96
+ # assume same year as start_at event (e.g. 2013 for 2013/14 season)
97
+ start.year
98
+ else
99
+ # assume year+1 as start_at event (e.g. 2014 for 2013/14 season)
100
+ start.year+1
101
+ end
102
+ end
103
+ Date.new( year,month,day )
104
+ else
105
+ raise ArgumentError, "unexpected date format; cannot parse >#{str}<"
106
+ end
107
+ end
108
+
109
+
110
+
111
+ def self._parse_date( str )
112
+ ## note - strip - leading/trailing spaces automatic - why? why not?
113
+ m = DATE_RE.match( str.strip )
114
+
115
+ if m && m.pre_match == '' && m.post_match == ''
116
+ ## return hash table with captured components
117
+ date = _build_date( m )
118
+ date
119
+ elsif m
120
+ ## note - match BUT not anchored to start and end-of-string!!!
121
+ ## report, error somehow??
122
+ nil
123
+ else
124
+ nil ## no match - return nil
125
+ end
126
+ end
127
+
128
+
129
+ end # class Lexer
130
+ end # module SportDb