qreport_time_parser 0.0.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.
@@ -0,0 +1,33 @@
1
+ require 'qreport/time_parser'
2
+
3
+ module Qreport
4
+ class ReportRunner
5
+ def self.time_parser
6
+ Thread.current['Qreport::ReportRunner.time_parser'] ||= Qreport::TimeParser.new
7
+ end
8
+ def self.time_parser= x
9
+ Thread.current['Qreport::ReportRunner.time_parser'] = x
10
+ end
11
+ module TimeParse
12
+ def time_parse value, time_parser = nil
13
+ p = time_parser || Qreport::ReportRunner.time_parser.dup
14
+ case value
15
+ when nil, ::Time
16
+ when :now
17
+ value = p.now
18
+ when String
19
+ value = p.parse(value)
20
+ end
21
+ case
22
+ when value.respond_to?(:to_range)
23
+ value = value.to_range
24
+ when value.respond_to?(:to_time)
25
+ value = value.to_time
26
+ end
27
+ value
28
+ end
29
+ extend self
30
+ end
31
+ include TimeParse
32
+ end
33
+ end
@@ -0,0 +1,441 @@
1
+ require 'time'
2
+ require 'rational'
3
+
4
+ module Qreport
5
+ class TimeParser
6
+ class Error < ::Exception
7
+ class Syntax < self
8
+ attr_accessor :position, :description
9
+ end
10
+ end
11
+
12
+ attr_accessor :input, :start, :result, :now, :debug, :unit_for_now
13
+
14
+ def initialize start = nil
15
+ @start = start
16
+ @unit_for_now = { :today => :day, :t => :now }
17
+ @debug = false
18
+ # @debug = true
19
+ initialize_copy nil
20
+ end
21
+
22
+ def initialize_copy x
23
+ @input = ''
24
+ @token = nil
25
+ @token_stack = [ ]
26
+ @taken_tokens = [ ]
27
+ end
28
+
29
+ def parse str, start = nil
30
+ start ||= @start
31
+ $stderr.puts "\n parse #{str.inspect} #{start.inspect}" if @debug
32
+ @input_orig = str.dup
33
+ @input = str.dup
34
+ @pos = 0
35
+ @p_depth = 1
36
+ @result = send(start || :p_start)
37
+ @result = @result.value if @result.respond_to?(:value)
38
+ $stderr.puts " parse #{str.inspect} #{start.inspect} => #{@result.inspect}\n\n" if @debug
39
+ @result
40
+ end
41
+
42
+ def self.def_p name, &blk
43
+ sel = :"p_#{name}"
44
+ _sel = :"_#{sel}"
45
+ define_method _sel, &blk
46
+ define_method sel do
47
+ _wrap_p! sel do
48
+ restore_tokens_on_failure!(sel) do
49
+ send(_sel)
50
+ end
51
+ end
52
+ end
53
+ sel
54
+ end
55
+
56
+ def_p :start do
57
+ # debugger
58
+ p_range or
59
+ p_time_expr or
60
+ raise Error
61
+ end
62
+
63
+ def_p :range do
64
+ if p_between and a = p_time_expr and p_and and b = p_time_expr
65
+ TimeRange.new(a, b)
66
+ end
67
+ end
68
+
69
+ def_p :between do
70
+ token.type == :range and
71
+ token.value == :between and
72
+ take_token.value
73
+ end
74
+
75
+ def_p :and do
76
+ token.type == :logical and
77
+ token.value == :and and
78
+ take_token.value
79
+ end
80
+
81
+ def_p :time_expr do
82
+ case
83
+ when v = p_numeric_relative
84
+ v
85
+ when v = p_time
86
+ v
87
+ when v = p_time_or_date_relative
88
+ v
89
+ when (r = p_relation and v = p_time_expr)
90
+ v += r
91
+ else
92
+ return nil
93
+ end
94
+
95
+ if op = p_operation and interval = p_interval
96
+ case op
97
+ when :+
98
+ v += interval
99
+ when :-
100
+ v -= interval
101
+ else
102
+ raise Error, op
103
+ end
104
+ end
105
+
106
+ v
107
+ end
108
+
109
+ # 10 sec before now
110
+ # 10 sec ago
111
+ def_p :numeric_relative do
112
+ if (interval = p_interval)
113
+ case
114
+ when (direction = p_relation and time = p_time_expr)
115
+ time + (interval * direction)
116
+ when (direction = p_relative)
117
+ TimeWithUnit.new(now, interval) + (interval * direction)
118
+ end
119
+ end
120
+ end
121
+
122
+ # 10 secs|day
123
+ def_p :interval do
124
+ case token.value
125
+ when Numeric
126
+ amount = take_token.value
127
+ case
128
+ when TimeInterval === token.value
129
+ interval = take_token.value
130
+ interval *= amount
131
+ end
132
+ when TimeInterval
133
+ interval = take_token.value
134
+ end
135
+ end
136
+
137
+ # before|after|since
138
+ def_p :relation do
139
+ token.type == :relation and take_token.value
140
+ end
141
+
142
+ # ago
143
+ def_p :relative do
144
+ token.type == :relative and take_token.value
145
+ end
146
+
147
+ # + interval
148
+ def_p :operation do
149
+ token.type == :operation and take_token.value
150
+ end
151
+
152
+ def_p :time do
153
+ TimeWithUnit === token.value and take_token.value
154
+ end
155
+
156
+ def_p :time_or_date_relative do
157
+ case
158
+ when tr = _p_time_relative
159
+ dr = _p_date_relative
160
+ when dr = _p_date_relative
161
+ tr = _p_time_relative
162
+ else
163
+ return nil
164
+ end
165
+
166
+ tr ||= dr
167
+ tr = tr.dup
168
+ if tr && dr && tr != dr
169
+ tr.merge!(dr)
170
+ end
171
+
172
+ unit = tr.unit
173
+ t = p_time_expr || now
174
+ # debugger
175
+ tr.merge!(t)
176
+ t = TimeWithUnit.new(tr.to_time, unit)
177
+ # $stderr.puts " tr = #{tr.inspect} dr = #{dr.inspect} => t = #{t.inspect}"
178
+ t
179
+ end
180
+
181
+ # 12pm|12:30a|12:34:56pm
182
+ def_p :time_relative do
183
+ token.type == :time_relative and
184
+ TimeRelative === token.value and
185
+ take_token.value
186
+ end
187
+
188
+ # 2001/01|2001-02-20
189
+ def_p :date_relative do
190
+ token.type == :date_relative and
191
+ TimeRelative === token.value and
192
+ take_token.value
193
+ end
194
+
195
+ def _wrap_p! sel, &blk
196
+ if @debug
197
+ $stderr.puts " #{' ' * @p_depth} #{sel} ... | #{token.inspect} #{token.value.inspect}"
198
+ @p_depth += 1
199
+ begin
200
+ restore_tokens_on_failure!(sel, &blk)
201
+ ensure
202
+ @p_depth -= 1
203
+ $stderr.puts " #{' ' * @p_depth} #{sel} => #{result.inspect} | #{token.inspect}"
204
+ end
205
+ else
206
+ restore_tokens_on_failure!(sel, &blk)
207
+ end
208
+ end
209
+
210
+ def restore_tokens_on_failure!(sel)
211
+ restore = true
212
+ (@taken_tokens_stack ||= [ ]) << @taken_tokens
213
+ @taken_tokens = [ ]
214
+ begin
215
+ result = yield
216
+ # $stderr.puts " #{sel.inspect} taken_tokens = #{@taken_tokens.inspect}"
217
+ restore = false if result
218
+ result
219
+ ensure
220
+ if restore && ! @taken_tokens.empty?
221
+ $stderr.puts " #{sel.inspect} restoring tokens #{@taken_tokens.inspect}" if @debug
222
+ @taken_tokens.reverse.each do | t |
223
+ push_token! t
224
+ end
225
+ end
226
+ @taken_tokens = @taken_tokens_stack.pop
227
+ end
228
+ end
229
+
230
+ ##############################################################
231
+
232
+ def token
233
+ @token ||=
234
+ (@token_stack.first ? @token_stack.shift : lex)
235
+ end
236
+
237
+ def take_token
238
+ t = token
239
+ @taken_tokens << t
240
+ @token = nil
241
+ t
242
+ end
243
+
244
+ def push_token! token
245
+ if @token
246
+ @token_stack.unshift @token
247
+ $stderr.puts "push_token! #{@token.inspect}" if @debug
248
+ end
249
+ @token = token
250
+ $stderr.puts "push_token! #{@token.inspect}" if @debug
251
+ self
252
+ end
253
+
254
+ def lex
255
+ debug = @debug
256
+ type = value = nil
257
+ @input.sub!(/\A(\s+)/, '')
258
+ pre_whitespace = $1
259
+ @pos += pre_whitespace.size if pre_whitespace
260
+ # $stderr.puts " @input = #{@input.inspect[0, 20]}..."; debugger
261
+ case @input
262
+ when ''
263
+ return EOS
264
+ when /\A(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(-[\d:]+)?)\b/ # iso8601
265
+ value = TimeWithUnit.new(Time.parse($1), nil)
266
+ when /\A(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(\.\d+)?(\s+[-+]?[\d]+)?)\b/ # Time#to_s
267
+ value = TimeWithUnit.new(Time.parse($1), nil)
268
+ when /\A(year\s+(\d+))/i
269
+ year = $2 && $2.to_i
270
+ value = TimeRelative.new
271
+ value.year = year
272
+ type = :date_relative
273
+ when /\A((\d{4})(?:([-\/])(0?[1-9]|1[0-2])(?:\3([0-2][0-9]|3[01]))?))\b/i
274
+ year = $2 && $2.to_i
275
+ sep = $3
276
+ mon = $4 && $4.to_i
277
+ day = $5 && $5.to_i
278
+ value = TimeRelative.new
279
+ value.year = year
280
+ value.mon = mon
281
+ value.day = day
282
+ type = :date_relative
283
+ when /\A((0[1-9]|1[0-2]|[1-9])(?:[-\/](0[1-9]|[1-2][0-9]|3[01]|[1-9])))\b/i
284
+ mon = $2 && $2.to_i
285
+ day = $3 && $3.to_i
286
+ value = TimeRelative.new
287
+ value.mon = mon
288
+ value.day = day
289
+ type = :date_relative
290
+ # debug = true
291
+ when /\A((0?[1-9]|1[0-2])(?::([0-5][0-9])(?::([0-5][0-9]|60))?)\s*(am?|pm?)?)\b/i
292
+ hour = $2.to_i
293
+ min = $3 && $3.to_i
294
+ sec = $4 && $4.to_i
295
+ meridian = ($5 || '').downcase
296
+ hour = 0 if hour == 12
297
+ hour += 12 if meridian.index('p')
298
+ value = TimeRelative.new
299
+ value.hour = hour
300
+ value.min = min
301
+ value.sec = sec
302
+ type = :time_relative
303
+ when /\A(\d\d?)([\/-])(\d\d\d\d)\b/i
304
+ mon = $1 && $1.to_i
305
+ sep = $2
306
+ year = $3 && $3.to_i
307
+ value = TimeRelative.new
308
+ value.year = year
309
+ value.mon = mon
310
+ type = :date_relative
311
+ when /\A((0?[1-9]|1[0-2])\s*(am?|pm?))\b/i
312
+ hour = $2.to_i
313
+ meridian = ($3 || '').downcase
314
+ hour = 0 if hour == 12
315
+ hour += 12 if meridian.index('p')
316
+ value = TimeRelative.new
317
+ value.hour = hour
318
+ type = :time_relative
319
+ when /\A([-+]?\d+\.\d*|\.\d+)/
320
+ value = $1.to_f
321
+ type = :number
322
+ when /\A([-+]?\d+)/
323
+ value = $1.to_i
324
+ type = :number
325
+ when /\A(\+|\-|plus\b|minus\b|in\b)/i
326
+ value = $1.downcase.to_sym
327
+ value = @@operation_alias[value] || value
328
+ type = :operation
329
+ when /\A(today|now|t)\b/i
330
+ case unit = get_unit_for_now($1)
331
+ when :now
332
+ value = TimeWithUnit.new(now, nil)
333
+ else
334
+ value = TimeWithUnit.new(now, unit)
335
+ end
336
+ when /\A(yesterday)\b/i
337
+ value = TimeWithUnit.new(now, :day) - 1
338
+ when /\A(tomorrow)\b/i
339
+ value = TimeWithUnit.new(now, :day) + 1
340
+ when /\A((this)\s+(#{TimeUnit::UNIT_REGEXP}))\b/io
341
+ value = TimeWithUnit.new(now, $3)
342
+ when /\A((previous|last|next)\s+(#{TimeUnit::UNIT_REGEXP}))\b/io
343
+ value = TimeWithUnit.new(now, $3) + TimeInterval.new($2, $3)
344
+ when /\A(#{TimeUnit::UNIT_REGEXP})\b/io
345
+ value = TimeInterval.new(1, $1)
346
+ type = :unit
347
+ when /\A(ago)\b/i
348
+ value = $1.downcase.to_sym
349
+ value = @@direction_alias[value]
350
+ type = :relative
351
+ when /\A(before|after|from|since)\b/i
352
+ value = $1.downcase.to_sym
353
+ value = @@direction_alias[value]
354
+ type = :relation
355
+ when /\A(between)\b/i
356
+ value = $1.downcase.to_sym
357
+ type = :range
358
+ when /\A(and|or)\b/i
359
+ value = $1.downcase.to_sym
360
+ type = :logical
361
+ else
362
+ desc = describe_current_parse_position
363
+ err = Error::Syntax.new("syntax error at position #{@pos}: #{desc.inspect}")
364
+ err.position = @pos
365
+ err.description = desc
366
+ raise err
367
+ end
368
+ token = $1
369
+ pos = @pos
370
+ @input[0, token.size] = ''
371
+ @pos += token.size
372
+ token.extend(Token)
373
+ token.pos = pos
374
+ token.pre = pre_whitespace
375
+ token.value = value
376
+ token.type = type
377
+ $stderr.puts " token => #{token.inspect}" if debug
378
+ token
379
+ end
380
+
381
+ def describe_current_parse_position
382
+ s = @input_orig.dup
383
+ s[@pos, 0] = " |^| "
384
+ s
385
+ end
386
+
387
+ @@operation_alias = {
388
+ :plus => :+,
389
+ :minus => :-,
390
+ :in => :+,
391
+ }
392
+
393
+ @@direction_alias = {
394
+ :ago => -1,
395
+ :before => -1,
396
+ :after => 1,
397
+ :from => 1,
398
+ :since => 1,
399
+ :later => 1,
400
+ }
401
+
402
+ def now
403
+ case @now
404
+ when Proc
405
+ @now = @now.call
406
+ end
407
+ @now ||= Time.now
408
+ end
409
+
410
+ def get_unit_for_now name
411
+ name = name.to_sym
412
+ @unit_for_now[name] || @unit_for_now[nil]
413
+ end
414
+
415
+ module Token
416
+ attr_accessor :value, :type, :pre, :pos
417
+ def inspect
418
+ "#<Token #{@type.inspect} #{super} #{@pos} #{@value.inspect}>"
419
+ end
420
+
421
+ def with_pre
422
+ @pre.to_s + self
423
+ end
424
+ end
425
+
426
+ EOS = Object.new
427
+ EOS.extend(Token)
428
+ def EOS.inspect
429
+ 'EOS'
430
+ end
431
+
432
+ end # class
433
+ end # module
434
+
435
+ require 'qreport/time_parser/time_unit'
436
+ require 'qreport/time_parser/time_interval'
437
+ require 'qreport/time_parser/time_with_unit'
438
+ require 'qreport/time_parser/time_relative'
439
+ require 'qreport/time_parser/time_range'
440
+
441
+