chrono_trigger 0.1.7 → 1.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/Gemfile +6 -0
- data/Gemfile.lock +167 -0
- data/LICENSE.txt +21 -0
- data/README.md +89 -0
- data/Rakefile +4 -50
- data/SUMMARY.md +5 -0
- data/bin/console +7 -0
- data/bin/loc +3 -0
- data/bin/setup +8 -0
- data/bin/standardize +4 -0
- data/lib/chrono_trigger.rb +27 -8
- data/lib/chrono_trigger/clock.rb +50 -0
- data/lib/chrono_trigger/config.rb +12 -0
- data/lib/chrono_trigger/event.rb +121 -0
- data/lib/chrono_trigger/schedule.rb +71 -0
- data/lib/chrono_trigger/timeline.rb +28 -0
- data/lib/chrono_trigger/version.rb +5 -0
- data/lib/chrono_trigger/worker.rb +10 -0
- metadata +168 -54
- data/History.txt +0 -13
- data/Manifest.txt +0 -24
- data/PostInstall.txt +0 -1
- data/README.rdoc +0 -59
- data/VERSION.yml +0 -4
- data/bin/chrono_trigger +0 -7
- data/chrono_trigger.gemspec +0 -69
- data/lib/chrono_trigger/cron_entry.rb +0 -71
- data/lib/chrono_trigger/process.rb +0 -37
- data/lib/chrono_trigger/runner.rb +0 -292
- data/lib/chrono_trigger/shell.rb +0 -36
- data/lib/chrono_trigger/tasks.rb +0 -3
- data/lib/chrono_trigger/trigger.rb +0 -127
- data/lib/tasks/chrono_trigger.rake +0 -14
- data/lib/triggers/test_triggers.rb +0 -31
- data/script/console +0 -10
- data/script/destroy +0 -14
- data/script/generate +0 -14
- data/test/test_chrono_trigger.rb +0 -11
- data/test/test_cron_entry.rb +0 -198
- data/test/test_helper.rb +0 -13
- data/test/test_shell.rb +0 -52
- data/test/test_trigger.rb +0 -172
- data/test/triggers.rb +0 -17
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ChronoTrigger
|
4
|
+
class Event
|
5
|
+
class << self
|
6
|
+
def schedule(*args)
|
7
|
+
id = SecureRandom.uuid
|
8
|
+
options = OpenStruct.new(
|
9
|
+
id: id,
|
10
|
+
scope: @scope,
|
11
|
+
repeats: @repeats || 1,
|
12
|
+
every: @every || 1.second,
|
13
|
+
at: @at,
|
14
|
+
before: @before,
|
15
|
+
after: @after
|
16
|
+
)
|
17
|
+
new(options, args)
|
18
|
+
end
|
19
|
+
|
20
|
+
def scope(value)
|
21
|
+
return self unless value
|
22
|
+
@scope = value if value.is_a?(String)
|
23
|
+
@scope = value.to_gid.to_s if value.is_a?(ActiveRecord::Base)
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def repeats(value)
|
28
|
+
return self unless value
|
29
|
+
@repeats = value.count if value.is_a?(Enumerator)
|
30
|
+
@repeats = value if value.is_a?(Integer) || value == :forever
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def every(value)
|
35
|
+
return self unless value
|
36
|
+
@every = value if value.is_a?(ActiveSupport::Duration)
|
37
|
+
@every = value.seconds if value.is_a?(Integer)
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def at(value)
|
42
|
+
return self unless value
|
43
|
+
value = Time.zone.parse(value) if value.is_a?(String)
|
44
|
+
@at = moment_in_the_future(value) if value.is_a?(ActiveSupport::TimeWithZone)
|
45
|
+
puts @at.class
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def before(value)
|
50
|
+
return self unless value
|
51
|
+
value = Time.zone.parse(value) if value.is_a?(String)
|
52
|
+
@before = moment_in_the_future(value) if value.is_a?(ActiveSupport::TimeWithZone)
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
def after(value)
|
57
|
+
return self unless value
|
58
|
+
value = Time.zone.parse(value) if value.is_a?(String)
|
59
|
+
@after = moment_in_the_future(value) if value.is_a?(ActiveSupport::TimeWithZone)
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def moment_in_the_future(time_with_zone)
|
66
|
+
Time.zone.today + time_with_zone.hour.hours + time_with_zone.min.minutes + time_with_zone.sec.seconds
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class LostInTimeError < StandardError
|
71
|
+
end
|
72
|
+
|
73
|
+
class TodayIsTomorrowsYesterday < StandardError
|
74
|
+
end
|
75
|
+
|
76
|
+
attr_reader :id, :scope, :args, :every, :before, :after, :purge
|
77
|
+
attr_accessor :repeats, :at
|
78
|
+
|
79
|
+
def initialize(options, args)
|
80
|
+
@id = options.id
|
81
|
+
@scope = options.scope
|
82
|
+
@repeats = options.repeats
|
83
|
+
@every = options.every
|
84
|
+
@at = options.at
|
85
|
+
@before = options.before
|
86
|
+
@after = options.after
|
87
|
+
@args = args
|
88
|
+
@purge = false
|
89
|
+
ChronoTrigger.schedule.add(self)
|
90
|
+
end
|
91
|
+
|
92
|
+
def ticks
|
93
|
+
ChronoTrigger::Clock.ticks
|
94
|
+
end
|
95
|
+
|
96
|
+
def purge!
|
97
|
+
@purge = true
|
98
|
+
end
|
99
|
+
|
100
|
+
def right_now
|
101
|
+
now = Time.zone.now
|
102
|
+
Time.zone.today + now.hour.hours + now.min.minutes + now.sec.seconds
|
103
|
+
end
|
104
|
+
|
105
|
+
def moment_in_the_future(time_with_zone)
|
106
|
+
raise(LostInTimeError, "Argument must be of type ActiveSupport::TimeWithZone eg. right_now + 1.second") unless time_with_zone.is_a?(ActiveSupport::TimeWithZone)
|
107
|
+
raise(TodayIsTomorrowsYesterday, "Time must be in the future eg. 1.second.from_now") unless time_with_zone.future?
|
108
|
+
Time.zone.today + time_with_zone.hour.hours + time_with_zone.min.minutes + time_with_zone.sec.seconds
|
109
|
+
end
|
110
|
+
|
111
|
+
def to_s
|
112
|
+
inspect
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def perform(args)
|
118
|
+
raise NotImplementedError
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ChronoTrigger
|
4
|
+
class Schedule
|
5
|
+
include Singleton
|
6
|
+
attr_reader :events
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@events = Concurrent::Array.new
|
10
|
+
@pool = Concurrent::Actor::Utils::Pool.spawn! "pool", 5 do |index|
|
11
|
+
Rails.logger.info "ChronoTrigger: Spawning worker schedule-#{index}"
|
12
|
+
ChronoTrigger::Worker.spawn name: "schedule-#{index}", supervise: true, args: []
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def add(event)
|
17
|
+
@events << event if event&.is_a?(ChronoTrigger::Event)
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def remove(uuid)
|
22
|
+
uuid_regex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
|
23
|
+
@events.each { |event| event.purge! if event.id == uuid } if uuid && uuid_regex.match?(uuid.to_s.downcase)
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def clear
|
28
|
+
@events.each { |event| event.purge! }
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def clear_scope(value)
|
33
|
+
return self unless value
|
34
|
+
scope = value if value.is_a?(String)
|
35
|
+
scope = value.to_gid.to_s if value.is_a?(ActiveRecord::Base)
|
36
|
+
@events.each { |event| event.purge! if event.scope == scope }
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
def process_events
|
41
|
+
now = right_now
|
42
|
+
events.each do |event|
|
43
|
+
if event.purge || (event.before && now >= event.before) || event.repeats == 0
|
44
|
+
@events.delete(event)
|
45
|
+
next
|
46
|
+
end
|
47
|
+
next if event.after && now < event.after
|
48
|
+
if event.at.nil? || event.at == now
|
49
|
+
@pool << event
|
50
|
+
event.at = now + event.every
|
51
|
+
event.repeats -= 1 unless event.repeats == :forever
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def refresh
|
57
|
+
now = right_now
|
58
|
+
events.each do |event|
|
59
|
+
next if event.at.nil?
|
60
|
+
event.at = nil if event.at < now
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def right_now
|
67
|
+
now = Time.zone.now
|
68
|
+
Time.zone.today + now.hour.hours + now.min.minutes + now.sec.seconds
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ChronoTrigger
|
4
|
+
module Timeline
|
5
|
+
extend ::ActiveSupport::Concern
|
6
|
+
|
7
|
+
class LostInTimeError < StandardError
|
8
|
+
end
|
9
|
+
|
10
|
+
class TodayIsTomorrowsYesterday < StandardError
|
11
|
+
end
|
12
|
+
|
13
|
+
def chrono_trigger
|
14
|
+
ChronoTrigger::Schedule.instance
|
15
|
+
end
|
16
|
+
|
17
|
+
def right_now
|
18
|
+
now = Time.zone.now
|
19
|
+
Time.zone.today + now.hour.hours + now.min.minutes + now.sec.seconds
|
20
|
+
end
|
21
|
+
|
22
|
+
def moment_in_the_future(time_with_zone)
|
23
|
+
raise(LostInTimeError, "Argument must be of type ActiveSupport::TimeWithZone eg. right_now + 1.second") unless time_with_zone.is_a?(ActiveSupport::TimeWithZone)
|
24
|
+
raise(TodayIsTomorrowsYesterday, "Time must be in the future eg. 1.second.from_now") unless time_with_zone.future?
|
25
|
+
Time.zone.today + time_with_zone.hour.hours + time_with_zone.min.minutes + time_with_zone.sec.seconds
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ChronoTrigger
|
4
|
+
class Worker < Concurrent::Actor::RestartingContext
|
5
|
+
def on_message(event)
|
6
|
+
Rails.logger.debug "ChronoTrigger: #{event.inspect}"
|
7
|
+
event.perform(*event.args) if event.at.nil? || (event.at && event.before.nil?) || (event.before && event.at < event.before)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
metadata
CHANGED
@@ -1,79 +1,193 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chrono_trigger
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 1.0.2
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
|
-
-
|
7
|
+
- leastbad
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
13
|
-
dependencies:
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
11
|
+
date: 2021-04-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: concurrent-ruby
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.1'
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 1.1.8
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "~>"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '1.1'
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 1.1.8
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: concurrent-ruby-edge
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0.6'
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 0.6.0
|
57
|
+
type: :runtime
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - "~>"
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0.6'
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: 0.6.0
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: magic_frozen_string_literal
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - "~>"
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: 1.2.0
|
74
|
+
type: :development
|
75
|
+
prerelease: false
|
76
|
+
version_requirements: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - "~>"
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: 1.2.0
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: pry
|
83
|
+
requirement: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - "~>"
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0.12'
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - "~>"
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0.12'
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: pry-nav
|
97
|
+
requirement: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - "~>"
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0.3'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - "~>"
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0.3'
|
109
|
+
- !ruby/object:Gem::Dependency
|
110
|
+
name: rake
|
111
|
+
requirement: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - "~>"
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '13.0'
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: 13.0.3
|
119
|
+
type: :development
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - "~>"
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '13.0'
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: 13.0.3
|
129
|
+
- !ruby/object:Gem::Dependency
|
130
|
+
name: standardrb
|
131
|
+
requirement: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - "~>"
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '1.0'
|
136
|
+
type: :development
|
137
|
+
prerelease: false
|
138
|
+
version_requirements: !ruby/object:Gem::Requirement
|
139
|
+
requirements:
|
140
|
+
- - "~>"
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '1.0'
|
143
|
+
description:
|
144
|
+
email:
|
145
|
+
- hello@leastbad.com
|
146
|
+
executables: []
|
18
147
|
extensions: []
|
19
|
-
extra_rdoc_files:
|
20
|
-
- README.rdoc
|
148
|
+
extra_rdoc_files: []
|
21
149
|
files:
|
22
|
-
-
|
23
|
-
-
|
24
|
-
-
|
25
|
-
- README.
|
150
|
+
- Gemfile
|
151
|
+
- Gemfile.lock
|
152
|
+
- LICENSE.txt
|
153
|
+
- README.md
|
26
154
|
- Rakefile
|
27
|
-
-
|
28
|
-
- bin/
|
29
|
-
-
|
155
|
+
- SUMMARY.md
|
156
|
+
- bin/console
|
157
|
+
- bin/loc
|
158
|
+
- bin/setup
|
159
|
+
- bin/standardize
|
30
160
|
- lib/chrono_trigger.rb
|
31
|
-
- lib/chrono_trigger/
|
32
|
-
- lib/chrono_trigger/
|
33
|
-
- lib/chrono_trigger/
|
34
|
-
- lib/chrono_trigger/
|
35
|
-
- lib/chrono_trigger/
|
36
|
-
- lib/chrono_trigger/
|
37
|
-
- lib/
|
38
|
-
|
39
|
-
|
40
|
-
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
- test/test_helper.rb
|
45
|
-
- test/test_shell.rb
|
46
|
-
- test/test_trigger.rb
|
47
|
-
- test/triggers.rb
|
48
|
-
homepage: ''
|
49
|
-
licenses: []
|
161
|
+
- lib/chrono_trigger/clock.rb
|
162
|
+
- lib/chrono_trigger/config.rb
|
163
|
+
- lib/chrono_trigger/event.rb
|
164
|
+
- lib/chrono_trigger/schedule.rb
|
165
|
+
- lib/chrono_trigger/timeline.rb
|
166
|
+
- lib/chrono_trigger/version.rb
|
167
|
+
- lib/chrono_trigger/worker.rb
|
168
|
+
homepage: https://chronotrigger.leastbad.com/
|
169
|
+
licenses:
|
170
|
+
- MIT
|
171
|
+
metadata:
|
172
|
+
source_code_uri: https://github.com/leastbad/chrono_trigger
|
173
|
+
documentation_uri: https://chronotrigger.leastbad.com/
|
50
174
|
post_install_message:
|
51
|
-
rdoc_options:
|
52
|
-
- --charset=UTF-8
|
175
|
+
rdoc_options: []
|
53
176
|
require_paths:
|
54
177
|
- lib
|
55
178
|
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
-
none: false
|
57
179
|
requirements:
|
58
|
-
- -
|
180
|
+
- - ">="
|
59
181
|
- !ruby/object:Gem::Version
|
60
182
|
version: '0'
|
61
183
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
-
none: false
|
63
184
|
requirements:
|
64
|
-
- -
|
185
|
+
- - ">="
|
65
186
|
- !ruby/object:Gem::Version
|
66
187
|
version: '0'
|
67
188
|
requirements: []
|
68
|
-
|
69
|
-
rubygems_version: 1.8.24
|
189
|
+
rubygems_version: 3.1.4
|
70
190
|
signing_key:
|
71
|
-
specification_version:
|
72
|
-
summary:
|
73
|
-
test_files:
|
74
|
-
- test/test_chrono_trigger.rb
|
75
|
-
- test/test_cron_entry.rb
|
76
|
-
- test/test_helper.rb
|
77
|
-
- test/test_shell.rb
|
78
|
-
- test/test_trigger.rb
|
79
|
-
- test/triggers.rb
|
191
|
+
specification_version: 4
|
192
|
+
summary: A clock-based scheduler that runs inside your Rails app.
|
193
|
+
test_files: []
|