qreport_time_parser 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+