Class: Symphony::Metronome::IntervalExpression

Inherits:
Object
  • Object
show all
Extended by:
Loggability
Includes:
Comparable, TimeFunctions
Defined in:
lib/symphony/metronome/intervalexpression.rb

Overview

Parse natural English expressions of times and intervals.

in 30 minutes
once an hour
every 15 minutes for 2 days
at 2014-05-01
at 2014-04-01 14:00:25
at 2pm
starting at 2pm once a day
start in 1 hour from now run every 5 seconds end at 11:15pm
every other hour
once a day ending in 1 week
run once a minute for an hour starting in 6 days
run each hour starting at 2010-01-05 09:00:00
10 times a minute for 2 days
run 45 times every hour
30 times per day
start at 2010-01-02 run 12 times and end on 2010-01-03
starting in an hour from now run 6 times a minute for 2 hours
beginning a day from now, run 30 times per minute and finish in 2 weeks
execute 12 times during the next 2 minutes

Constant Summary collapse

COMMON_DECORATORS =

Words/phrases in the expression that we’ll strip/ignore before parsing.

[ 'and', 'then', /\s+from now/, 'the next' ]

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(expression, base) ⇒ IntervalExpression

:nodoc:



2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
# File 'lib/symphony/metronome/intervalexpression.rb', line 2172

def initialize( expression, base ) # :nodoc:
  @exp  = expression
  @data = expression.to_s.unpack( 'c*' )
  @base = base

  @valid      = false
  @recurring  = false
  @starting   = nil
  @interval   = nil
  @multiplier = nil
  @ending     = nil
end

Class Attribute Details

._interval_expression_eof_actionsObject

Returns the value of attribute _interval_expression_eof_actions.



1767
1768
1769
# File 'lib/symphony/metronome/intervalexpression.rb', line 1767

def _interval_expression_eof_actions
  @_interval_expression_eof_actions
end

._interval_expression_index_offsetsObject

Returns the value of attribute _interval_expression_index_offsets.



793
794
795
# File 'lib/symphony/metronome/intervalexpression.rb', line 793

def _interval_expression_index_offsets
  @_interval_expression_index_offsets
end

._interval_expression_key_offsetsObject

Returns the value of attribute _interval_expression_key_offsets.



47
48
49
# File 'lib/symphony/metronome/intervalexpression.rb', line 47

def _interval_expression_key_offsets
  @_interval_expression_key_offsets
end

._interval_expression_range_lengthsObject

Returns the value of attribute _interval_expression_range_lengths.



675
676
677
# File 'lib/symphony/metronome/intervalexpression.rb', line 675

def _interval_expression_range_lengths
  @_interval_expression_range_lengths
end

._interval_expression_single_lengthsObject

Returns the value of attribute _interval_expression_single_lengths.



557
558
559
# File 'lib/symphony/metronome/intervalexpression.rb', line 557

def _interval_expression_single_lengths
  @_interval_expression_single_lengths
end

._interval_expression_trans_actionsObject

Returns the value of attribute _interval_expression_trans_actions.



1339
1340
1341
# File 'lib/symphony/metronome/intervalexpression.rb', line 1339

def _interval_expression_trans_actions
  @_interval_expression_trans_actions
end

._interval_expression_trans_keysObject

Returns the value of attribute _interval_expression_trans_keys.



165
166
167
# File 'lib/symphony/metronome/intervalexpression.rb', line 165

def _interval_expression_trans_keys
  @_interval_expression_trans_keys
end

._interval_expression_trans_targsObject

Returns the value of attribute _interval_expression_trans_targs.



911
912
913
# File 'lib/symphony/metronome/intervalexpression.rb', line 911

def _interval_expression_trans_targs
  @_interval_expression_trans_targs
end

.interval_expression_en_mainObject

Returns the value of attribute interval_expression_en_main.



1898
1899
1900
# File 'lib/symphony/metronome/intervalexpression.rb', line 1898

def interval_expression_en_main
  @interval_expression_en_main
end

.interval_expression_errorObject

Returns the value of attribute interval_expression_error.



1893
1894
1895
# File 'lib/symphony/metronome/intervalexpression.rb', line 1893

def interval_expression_error
  @interval_expression_error
end

.interval_expression_first_finalObject

Returns the value of attribute interval_expression_first_final.



1889
1890
1891
# File 'lib/symphony/metronome/intervalexpression.rb', line 1889

def interval_expression_first_final
  @interval_expression_first_final
end

.interval_expression_startObject

Returns the value of attribute interval_expression_start.



1885
1886
1887
# File 'lib/symphony/metronome/intervalexpression.rb', line 1885

def interval_expression_start
  @interval_expression_start
end

Instance Attribute Details

#endingObject (readonly)

The valid end time for the schedule (for recurring events)



2200
2201
2202
# File 'lib/symphony/metronome/intervalexpression.rb', line 2200

def ending
  @ending
end

#intervalObject (readonly)

The interval to wait before the event should be acted on.



2203
2204
2205
# File 'lib/symphony/metronome/intervalexpression.rb', line 2203

def interval
  @interval
end

#multiplierObject (readonly)

An optional interval multipler for expressing counts.



2206
2207
2208
# File 'lib/symphony/metronome/intervalexpression.rb', line 2206

def multiplier
  @multiplier
end

#recurringObject (readonly)

Does this event repeat?



2194
2195
2196
# File 'lib/symphony/metronome/intervalexpression.rb', line 2194

def recurring
  @recurring
end

#startingObject (readonly)

The valid start time for the schedule (for recurring events)



2197
2198
2199
# File 'lib/symphony/metronome/intervalexpression.rb', line 2197

def starting
  @starting
end

#validObject (readonly)

Is the schedule expression parsable?



2191
2192
2193
# File 'lib/symphony/metronome/intervalexpression.rb', line 2191

def valid
  @valid
end

Class Method Details

.parse(exp, time = nil) ⇒ Object

Parse a schedule expression exp.

Parsing defaults to Time.now(), but if passed a time object, all contexual times (2pm) are relative to it. If you know when an expression was generated, you can ‘reconstitute’ an interval object this way.



1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
# File 'lib/symphony/metronome/intervalexpression.rb', line 1919

def self::parse( exp, time=nil )

  # Normalize the expression before parsing
  #
  exp = exp.downcase.
    gsub( /(?:[^[a-z][0-9][\.\-:]\s]+)/, '' ).   # . : - a-z 0-9 only
    gsub( Regexp.union(COMMON_DECORATORS), '' ). # remove common decorator words
    gsub( /\s+/, ' ' ).                          # collapse whitespace
    gsub( /([:\-])+/, '\1' ).                    # collapse multiple - or : chars
    gsub( /\.+$/, '' )                           # trailing periods

  event = new( exp, time || Time.now )
  data  = event.instance_variable_get( :@data )

  # Ragel interface variables
  #
  key    = ''
  mark   = 0
  
begin
p ||= 0
pe ||= data.length
cs = interval_expression_start
end

  eof = pe
  
begin
testEof = false
_klen, _trans, _keys = nil
_goto_level = 0
_resume = 10
_eof_trans = 15
_again = 20
_test_eof = 30
_out = 40
while true
if _goto_level <= 0
if p == pe
  _goto_level = _test_eof
  next
end
if cs == 0
  _goto_level = _out
  next
end
end
if _goto_level <= _resume
_keys = _interval_expression_key_offsets[cs]
_trans = _interval_expression_index_offsets[cs]
_klen = _interval_expression_single_lengths[cs]
_break_match = false

begin
  if _klen > 0
     _lower = _keys
     _upper = _keys + _klen - 1

     loop do
        break if _upper < _lower
        _mid = _lower + ( (_upper - _lower) >> 1 )

        if data[p].ord < _interval_expression_trans_keys[_mid]
           _upper = _mid - 1
        elsif data[p].ord > _interval_expression_trans_keys[_mid]
           _lower = _mid + 1
        else
           _trans += (_mid - _keys)
           _break_match = true
           break
        end
     end # loop
     break if _break_match
     _keys += _klen
     _trans += _klen
  end
  _klen = _interval_expression_range_lengths[cs]
  if _klen > 0
     _lower = _keys
     _upper = _keys + (_klen << 1) - 2
     loop do
        break if _upper < _lower
        _mid = _lower + (((_upper-_lower) >> 1) & ~1)
        if data[p].ord < _interval_expression_trans_keys[_mid]
          _upper = _mid - 2
        elsif data[p].ord > _interval_expression_trans_keys[_mid+1]
          _lower = _mid + 2
        else
          _trans += ((_mid - _keys) >> 1)
          _break_match = true
          break
        end
     end # loop
     break if _break_match
     _trans += _klen
  end
end while false
cs = _interval_expression_trans_targs[_trans];

if _interval_expression_trans_actions[_trans] != 0

  case _interval_expression_trans_actions[_trans] 
when 2 then
  begin
mark = p    end
when 1 then
  begin
event.instance_variable_set( :@valid, false )     end
when 3 then
  begin
event.instance_variable_set( :@recurring, true )    end
when 4 then
  begin

  time = event.send( :extract, mark, p - mark )
  event.send( :set_starting, time, :time )
    end
when 5 then
  begin

  interval = event.send( :extract, mark, p - mark )
  event.send( :set_starting, interval, :interval )
    end
when 9 then
  begin

  interval = event.send( :extract, mark, p - mark )
  event.send( :set_interval, interval, :interval )
    end
when 7 then
  begin

  multiplier = event.send( :extract, mark, p - mark ).sub( / times/, '' )
  event.instance_variable_set( :@multiplier, multiplier.to_i )
    end
when 14 then
  begin

  time = event.send( :extract, mark, p - mark )
  event.send( :set_ending, time, :time )
    end
when 15 then
  begin

  interval = event.send( :extract, mark, p - mark )
  event.send( :set_ending, interval, :interval )
    end
  end # action switch 
end

end
if _goto_level <= _again
if cs == 0
  _goto_level = _out
  next
end
p += 1
if p != pe
  _goto_level = _resume
  next
end
end
if _goto_level <= _test_eof
if p == eof
begin
  case ( _interval_expression_eof_actions[cs] )
when 1 then
  begin
event.instance_variable_set( :@valid, false )     end
when 10 then
  begin

  time = event.send( :extract, mark, p - mark )
  event.send( :set_starting, time, :time )
    end
  begin
event.instance_variable_set( :@valid, true )    end
when 13 then
  begin

  interval = event.send( :extract, mark, p - mark )
  event.send( :set_starting, interval, :interval )
    end
  begin
event.instance_variable_set( :@valid, true )    end
when 16 then
  begin

  time = event.send( :extract, mark, p - mark )
  event.send( :set_interval, time, :time )
    end
  begin
event.instance_variable_set( :@valid, true )    end
when 8 then
  begin

  interval = event.send( :extract, mark, p - mark )
  event.send( :set_interval, interval, :interval )
    end
  begin
event.instance_variable_set( :@valid, true )    end
when 6 then
  begin

  multiplier = event.send( :extract, mark, p - mark ).sub( / times/, '' )
  event.instance_variable_set( :@multiplier, multiplier.to_i )
    end
  begin
event.instance_variable_set( :@valid, true )    end
when 11 then
  begin

  time = event.send( :extract, mark, p - mark )
  event.send( :set_ending, time, :time )
    end
  begin
event.instance_variable_set( :@valid, true )    end
when 12 then
  begin

  interval = event.send( :extract, mark, p - mark )
  event.send( :set_ending, interval, :interval )
    end
  begin
event.instance_variable_set( :@valid, true )    end
  end
end
end

end
if _goto_level <= _out
  break
end
end
end


  # Attach final time logic and sanity checks.
  event.send( :finalize )

  return event
end

Instance Method Details

#<=>(other) ⇒ Object

Comparable interface, order by interval, ‘soonest’ first.



2257
2258
2259
# File 'lib/symphony/metronome/intervalexpression.rb', line 2257

def <=>( other )
  return self.interval <=> other.interval
end

#fire?Boolean

If this interval is on a stack somewhere and ready to fire, is it okay to do so based on the specified expression criteria?

Returns true if it should fire, false if it should not but could at a later attempt, and nil if the interval has expired.

Returns:

  • (Boolean)


2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
# File 'lib/symphony/metronome/intervalexpression.rb', line 2217

def fire?
  now = Time.now

  # Interval has expired.
  return nil if self.ending && now > self.ending

  # Interval is not yet in its current time window.
  return false if self.starting - now > 0

  # Looking good.
  return true
end

#inspectObject

Inspection string.



2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
# File 'lib/symphony/metronome/intervalexpression.rb', line 2240

def inspect
  return ( "<%s:0x%08x valid:%s recur:%s expression:%p " +
        "starting:%p interval:%p ending:%p>" ) % [
    self.class.name,
    self.object_id * 2,
    self.valid,
    self.recurring,
    self.to_s,
    self.starting,
    self.interval,
    self.ending
  ]
end

#to_sObject

Just return the original event expression.



2233
2234
2235
# File 'lib/symphony/metronome/intervalexpression.rb', line 2233

def to_s
  return @exp
end