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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/Guardfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +187 -0
- data/Rakefile +13 -0
- data/example/example-gen.rb +43 -0
- data/example/examples.rb +152 -0
- data/lib/qreport/report_runner/time_parse.rb +33 -0
- data/lib/qreport/time_parser.rb +441 -0
- data/lib/qreport/time_parser/examples.rb +64 -0
- data/lib/qreport/time_parser/time_interval.rb +117 -0
- data/lib/qreport/time_parser/time_range.rb +46 -0
- data/lib/qreport/time_parser/time_relative.rb +77 -0
- data/lib/qreport/time_parser/time_unit.rb +110 -0
- data/lib/qreport/time_parser/time_with_unit.rb +155 -0
- data/lib/qreport_time_parser.rb +5 -0
- data/lib/qreport_time_parser/version.rb +3 -0
- data/qreport_time_parser.gemspec +27 -0
- data/spec/lib/qreport/report_runner/time_parse_spec.rb +47 -0
- data/spec/lib/qreport/time_parser/time_interval_spec.rb +45 -0
- data/spec/lib/qreport/time_parser/time_range_spec.rb +14 -0
- data/spec/lib/qreport/time_parser/time_with_unit_spec.rb +26 -0
- data/spec/lib/qreport/time_parser_spec.rb +38 -0
- data/spec/spec_helper.rb +14 -0
- metadata +159 -0
@@ -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
|
+
|