yuuki 0.1.12 → 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 +4 -4
- data/.github/workflows/ci.yml +50 -0
- data/.rspec +1 -0
- data/.rubocop.yml +21 -0
- data/README.md +116 -15
- data/lib/yuuki/caller.rb +28 -83
- data/lib/yuuki/periodic_caller.rb +51 -0
- data/lib/yuuki/runner.rb +59 -45
- data/lib/yuuki/version.rb +1 -1
- data/lib/yuuki.rb +1 -0
- data/sig/yuuki/caller.rbs +33 -15
- data/sig/yuuki/error.rbs +0 -3
- data/sig/yuuki/periodic_caller.rbs +18 -0
- data/sig/yuuki/runner.rbs +41 -11
- data/sig/yuuki/version.rbs +0 -3
- data/sig/yuuki.rbs +10 -0
- data/yuuki.gemspec +7 -4
- metadata +41 -10
- data/.DS_Store +0 -0
- data/lib/yuuki/periodic.rb +0 -80
- data/sig/yuuki/periodic.rbs +0 -19
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 73ee236ec079617db802006ec29558cb42cbeb17143487fb5d03f9b0781e2211
|
|
4
|
+
data.tar.gz: 507eaffb469b260061af77703546bfb20bb4c9e5c8d62c26b946a861a2c69f36
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 70b198d6f2b79d3df1a27bd750d5ded8d7a1a827b6078f90f34b03ff701716d60a69dd0baa5e2774c4da0b2af7ccbc4b46fb9139056d301a6cfa59b5adcbe714
|
|
7
|
+
data.tar.gz: 7a8cdefa1d6aa71c65f45b6dda02e6b6987435d82efc821a31a3d40d2921443ad69088bd7e83a339aed22462894ac33afa84416f20af93ecb1468c9895a497d2
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
rspec:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
ruby: ['3.0', 'ruby']
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: ruby/setup-ruby@v1
|
|
17
|
+
with:
|
|
18
|
+
ruby-version: ${{ matrix.ruby }}
|
|
19
|
+
bundler-cache: true
|
|
20
|
+
- run: bundle exec rspec
|
|
21
|
+
|
|
22
|
+
rubocop:
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
- uses: ruby/setup-ruby@v1
|
|
27
|
+
with:
|
|
28
|
+
ruby-version: ruby
|
|
29
|
+
bundler-cache: true
|
|
30
|
+
- uses: reviewdog/action-rubocop@v2
|
|
31
|
+
with:
|
|
32
|
+
github_token: ${{ secrets.github_token }}
|
|
33
|
+
reporter: ${{ github.event_name == 'pull_request' && 'github-pr-review' || 'github-check' }}
|
|
34
|
+
skip_install: true
|
|
35
|
+
use_bundler: true
|
|
36
|
+
fail_level: any
|
|
37
|
+
rubocop_flags: lib
|
|
38
|
+
|
|
39
|
+
rbs:
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
strategy:
|
|
42
|
+
matrix:
|
|
43
|
+
ruby: ['3.0', 'ruby']
|
|
44
|
+
steps:
|
|
45
|
+
- uses: actions/checkout@v4
|
|
46
|
+
- uses: ruby/setup-ruby@v1
|
|
47
|
+
with:
|
|
48
|
+
ruby-version: ${{ matrix.ruby }}
|
|
49
|
+
bundler-cache: true
|
|
50
|
+
- run: bundle exec rbs -I sig validate
|
data/.rspec
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
--require spec_helper
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
AllCops:
|
|
3
|
+
TargetRubyVersion: 3.0
|
|
4
|
+
Metrics/AbcSize:
|
|
5
|
+
Enabled: false
|
|
6
|
+
Metrics/BlockLength:
|
|
7
|
+
Enabled: false
|
|
8
|
+
Metrics/ClassLength:
|
|
9
|
+
Enabled: false
|
|
10
|
+
Metrics/CyclomaticComplexity:
|
|
11
|
+
Enabled: false
|
|
12
|
+
Metrics/MethodLength:
|
|
13
|
+
Enabled: false
|
|
14
|
+
Metrics/PerceivedComplexity:
|
|
15
|
+
Enabled: false
|
|
16
|
+
Style/Documentation:
|
|
17
|
+
Enabled: false
|
|
18
|
+
Style/NumericPredicate:
|
|
19
|
+
EnforcedStyle: comparison
|
|
20
|
+
Style/SpecialGlobalVars:
|
|
21
|
+
EnforcedStyle: use_perl_names
|
data/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
A caller / runner framework for Ruby.
|
|
4
4
|
|
|
5
|
+
Yuuki lets you tag methods of a class and invoke them by tag. Arguments are delivered to each method by parameter name, and methods can be run in threads, ordered by priority, or executed periodically.
|
|
6
|
+
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
7
9
|
Add this line to your application's Gemfile:
|
|
@@ -20,33 +22,132 @@ Or install it yourself as:
|
|
|
20
22
|
|
|
21
23
|
## Usage
|
|
22
24
|
|
|
25
|
+
### Basics
|
|
26
|
+
|
|
27
|
+
Define a runner class that inherits `Yuuki::Runner`. A method named `on_<tag>` is automatically registered with the tag `<tag>`. Any other method can be registered with the `on` decorator, which applies to the next method definition.
|
|
28
|
+
|
|
23
29
|
```ruby
|
|
24
30
|
require 'yuuki'
|
|
25
31
|
|
|
26
|
-
class
|
|
27
|
-
|
|
32
|
+
class Greeter < Yuuki::Runner
|
|
33
|
+
# registered with the tag :greet (taken from the method name)
|
|
34
|
+
def on_greet
|
|
35
|
+
puts 'hello!'
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# registered with the tags :greet and :farewell
|
|
39
|
+
on :greet
|
|
40
|
+
on :farewell
|
|
41
|
+
def say_goodbye
|
|
42
|
+
puts 'goodbye!'
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
yuuki = Yuuki::Caller.new(Greeter)
|
|
47
|
+
yuuki.run(:greet)
|
|
48
|
+
# hello!
|
|
49
|
+
# goodbye!
|
|
50
|
+
|
|
51
|
+
yuuki.run(:farewell)
|
|
52
|
+
# goodbye!
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`Yuuki::Caller.new` (and `#add`) accepts runner classes and runner instances. A class is instantiated automatically. Inside a runner method, `yuuki` returns the caller the instance belongs to.
|
|
56
|
+
|
|
57
|
+
### Passing arguments
|
|
58
|
+
|
|
59
|
+
Keyword arguments given to `run` are delivered to each method by parameter name — no matter whether the parameter is positional or keyword. Extra arguments are simply ignored, and a missing required parameter raises `Yuuki::Error`. A block given to `run` is forwarded as the block of each method.
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
class Worker < Yuuki::Runner
|
|
63
|
+
def on_process(name, size: 1)
|
|
64
|
+
puts "processing #{name} (size: #{size})"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
yuuki = Yuuki::Caller.new(Worker)
|
|
69
|
+
yuuki.run(:process, name: 'job', size: 3, unused: 42)
|
|
70
|
+
# processing job (size: 3)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Decorators
|
|
74
|
+
|
|
75
|
+
Decorators are class methods that annotate the **next** method definition.
|
|
76
|
+
|
|
77
|
+
| Decorator | Effect |
|
|
78
|
+
| --- | --- |
|
|
79
|
+
| `on :tag` | Adds a tag. Can be specified multiple times. |
|
|
80
|
+
| `priority n` | Runs methods with higher priority first (default 0). Also used as `Thread#priority` for threaded methods. |
|
|
81
|
+
| `thread` | Runs the method in a new thread. |
|
|
82
|
+
| `periodic interval` | Runs the method every `interval` seconds (`Yuuki::PeriodicCaller` only). |
|
|
83
|
+
| `first_run` | Runs the method once on startup (`Yuuki::PeriodicCaller` only). |
|
|
28
84
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
85
|
+
### Threading
|
|
86
|
+
|
|
87
|
+
Methods marked with `thread` run in their own thread; `run` returns without waiting for them.
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
class Background < Yuuki::Runner
|
|
91
|
+
on :heavy
|
|
92
|
+
thread
|
|
93
|
+
def heavy_task
|
|
94
|
+
sleep 1
|
|
95
|
+
puts 'done'
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
yuuki = Yuuki::Caller.new(Background)
|
|
100
|
+
yuuki.run(:heavy) # returns immediately
|
|
101
|
+
yuuki.running? # => true
|
|
102
|
+
yuuki.join # waits for all running threads
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Periodic execution
|
|
106
|
+
|
|
107
|
+
`Yuuki::PeriodicCaller` runs `periodic` methods repeatedly and `first_run` methods once on startup. It is not required by default:
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
require 'yuuki/periodic_caller'
|
|
111
|
+
|
|
112
|
+
class Watcher < Yuuki::Runner
|
|
113
|
+
first_run
|
|
114
|
+
def setup
|
|
115
|
+
puts 'starting'
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
periodic 60
|
|
119
|
+
def check
|
|
120
|
+
puts 'checking...'
|
|
32
121
|
end
|
|
33
122
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
puts 'b'
|
|
123
|
+
periodic 86_400
|
|
124
|
+
def daily
|
|
125
|
+
puts 'a new day has come'
|
|
38
126
|
end
|
|
39
127
|
end
|
|
40
128
|
|
|
41
|
-
yuuki = Yuuki::
|
|
42
|
-
yuuki.
|
|
43
|
-
#
|
|
44
|
-
|
|
129
|
+
yuuki = Yuuki::PeriodicCaller.new(Watcher)
|
|
130
|
+
yuuki.on_error { |error| warn error.message }
|
|
131
|
+
yuuki.run # blocks forever
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Intervals are aligned to wall-clock boundaries in local time: `periodic 60` fires at the top of every minute, and `periodic 86_400` fires at local midnight (pass a GMT offset as the first argument of `run` to change the time zone). Intervals shorter than 1 second are not supported.
|
|
135
|
+
|
|
136
|
+
An exception raised by a (non-threaded) runner method is passed to the `on_error` callback and the loop keeps running. Without `on_error`, the exception stops `run`.
|
|
45
137
|
|
|
46
|
-
|
|
47
|
-
|
|
138
|
+
### Loading runner files
|
|
139
|
+
|
|
140
|
+
`Yuuki::Caller.require_dir` requires all `*.rb` files in a directory:
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
Yuuki::Caller.require_dir('runners')
|
|
144
|
+
Yuuki::Caller.require_dir('runners', recursive: true)
|
|
48
145
|
```
|
|
49
146
|
|
|
147
|
+
### Graceful shutdown with Yoshinon
|
|
148
|
+
|
|
149
|
+
By default, each method invocation is wrapped in a [Yoshinon](https://github.com/Ishotihadus/yoshinon) lock so that a trapped signal waits for running methods to finish. Pass `use_yoshinon: false` to `Yuuki::Caller.new` to disable this.
|
|
150
|
+
|
|
50
151
|
## Contributing
|
|
51
152
|
|
|
52
153
|
Bug reports and pull requests are welcome on GitHub at [https://github.com/ishotihadus/yuuki](https://github.com/ishotihadus/yuuki).
|
data/lib/yuuki/caller.rb
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
3
|
+
require 'set'
|
|
4
4
|
require 'yoshinon'
|
|
5
|
+
require 'yuuki/runner'
|
|
5
6
|
|
|
6
7
|
module Yuuki
|
|
7
8
|
class Caller
|
|
8
|
-
# requires all the rb files in the given directory
|
|
9
|
-
# @param [String] require_dir directory
|
|
10
|
-
# @param [Boolean] recursive
|
|
11
9
|
def self.require_dir(require_dir, recursive: false)
|
|
12
10
|
Dir.glob(recursive ? "#{require_dir}/**/*.rb" : "#{require_dir}/*.rb").each { |file| require file }
|
|
13
11
|
end
|
|
14
12
|
|
|
15
|
-
# @param [Object] instances
|
|
16
13
|
def initialize(*instances, use_yoshinon: true)
|
|
17
14
|
@instances = Set.new
|
|
18
15
|
@threads = []
|
|
@@ -20,110 +17,53 @@ module Yuuki
|
|
|
20
17
|
@use_yoshinon = use_yoshinon
|
|
21
18
|
end
|
|
22
19
|
|
|
23
|
-
# adds instances to yuuki
|
|
24
|
-
# @param [Object] instances
|
|
25
20
|
def add(*instances)
|
|
26
21
|
instances.each do |instance|
|
|
27
|
-
# create instance if class is given
|
|
28
22
|
if instance.is_a?(Class)
|
|
29
23
|
klass = instance
|
|
30
|
-
|
|
31
|
-
unless klass.singleton_class.include?(Yuuki::Runner)
|
|
32
|
-
raise Yuuki::Error, 'Runner instance must be extend Yuuki::Runner'
|
|
33
|
-
end
|
|
24
|
+
raise Yuuki::Error, 'Runner instance must be inherit Yuuki::Runner' unless klass < Yuuki::Runner
|
|
34
25
|
|
|
26
|
+
# allocate + explicit initialize so that @yuuki is already set when
|
|
27
|
+
# the runner's #initialize runs
|
|
35
28
|
instance = instance.allocate
|
|
36
29
|
instance.instance_variable_set(:@yuuki, self)
|
|
37
30
|
instance.send(:initialize)
|
|
38
31
|
else
|
|
39
|
-
|
|
40
|
-
unless instance.class.singleton_class.include?(Yuuki::Runner)
|
|
41
|
-
raise Yuuki::Error, 'Runner instance must be extend Yuuki::Runner'
|
|
42
|
-
end
|
|
32
|
+
raise Yuuki::Error, 'Runner instance must be inherit Yuuki::Runner' unless instance.is_a?(Yuuki::Runner)
|
|
43
33
|
|
|
44
|
-
# add @yuuki to the instance
|
|
45
34
|
instance.instance_variable_set(:@yuuki, self)
|
|
46
35
|
end
|
|
47
|
-
|
|
48
|
-
# regist
|
|
49
36
|
@instances << instance
|
|
50
37
|
end
|
|
51
38
|
end
|
|
52
39
|
|
|
53
|
-
# returns
|
|
54
|
-
#
|
|
55
|
-
def
|
|
40
|
+
# NOTE: intentionally shadows Object#methods; returns [Method, meta] pairs
|
|
41
|
+
# sorted by priority (highest first)
|
|
42
|
+
def methods
|
|
56
43
|
list = @instances.flat_map do |instance|
|
|
57
|
-
methods = instance.class.
|
|
58
|
-
methods.
|
|
44
|
+
methods = instance.class.yuuki_methods
|
|
45
|
+
methods.map { |sig, meta| [instance.method(sig), meta] }
|
|
59
46
|
end
|
|
60
|
-
list.sort_by { |_method, meta| -(meta[:priority] || 0) }
|
|
47
|
+
list.sort_by! { |_method, meta| -(meta[:priority] || 0) }
|
|
48
|
+
list
|
|
61
49
|
end
|
|
62
50
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
methods = instance.class.instance_variable_get(:@yuuki_methods)
|
|
67
|
-
methods.select { |_sig, meta| meta[:enabled] }.flat_map { |_sig, meta| meta[:tags] }
|
|
51
|
+
def run(*tags, **args, &block)
|
|
52
|
+
selector = proc do |_method, meta|
|
|
53
|
+
meta[:tags] && !(meta[:tags] & tags).empty?
|
|
68
54
|
end
|
|
69
|
-
|
|
70
|
-
tags.each { |e| ret += e if e }
|
|
71
|
-
ret
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
# runs all methods
|
|
75
|
-
# @param [Object] args arguments
|
|
76
|
-
def run(**args, &)
|
|
77
|
-
run_internal(runners, args, &)
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
# runs all selected methods
|
|
81
|
-
# @param [Proc] proc_select
|
|
82
|
-
# @param [Object] args arguments
|
|
83
|
-
def run_select(proc_select, **args, &)
|
|
84
|
-
run_internal(runners.select(&proc_select), args, &)
|
|
55
|
+
run_select(selector, **args, &block)
|
|
85
56
|
end
|
|
86
57
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
@ignore_tag_error = enabled
|
|
58
|
+
def run_select(selector, **args, &block)
|
|
59
|
+
run_internal(methods.select(&selector), args, &block)
|
|
90
60
|
end
|
|
91
61
|
|
|
92
|
-
# runs all methods with the specified tags
|
|
93
|
-
# @param [Array<Symbol>] tags
|
|
94
|
-
# @param [Object] args arguments
|
|
95
|
-
def run_tag(*tags, **args, &)
|
|
96
|
-
t = self.tags
|
|
97
|
-
tags.each do |e|
|
|
98
|
-
next if t.include?(e)
|
|
99
|
-
raise Yuuki::Error, "tag `#{e}` is not associated" unless @ignore_tag_error
|
|
100
|
-
|
|
101
|
-
warn "Yuuki Warning: tag `#{e}` is not associated"
|
|
102
|
-
end
|
|
103
|
-
run_select(proc { |_method, meta| meta[:tags]&.intersect?(tags) }, **args, &)
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
# runs the specific method
|
|
107
|
-
# @param [Class, nil] klass
|
|
108
|
-
# @param [Symbol, nil] method_sig method name
|
|
109
|
-
# @param [Object] args arguments
|
|
110
|
-
def run_method(klass, method_sig, **args, &)
|
|
111
|
-
select_proc = proc do |method, _meta|
|
|
112
|
-
flag_klass = klass ? method.receiver.instance_of?(klass) : true
|
|
113
|
-
flag_method = method_sig ? method.name == method_sig : true
|
|
114
|
-
flag_klass && flag_method
|
|
115
|
-
end
|
|
116
|
-
run_select(select_proc, **args, &)
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# joins all runnning threads
|
|
120
62
|
def join
|
|
121
63
|
@threads.each(&:join)
|
|
122
64
|
@threads.select!(&:alive?)
|
|
123
65
|
end
|
|
124
66
|
|
|
125
|
-
# returns whether any thread is alive
|
|
126
|
-
# @return [Boolean]
|
|
127
67
|
def alive?
|
|
128
68
|
@threads.select!(&:alive?)
|
|
129
69
|
!@threads.empty?
|
|
@@ -147,10 +87,13 @@ module Yuuki
|
|
|
147
87
|
end
|
|
148
88
|
end
|
|
149
89
|
|
|
90
|
+
# Maps the args hash onto the method's parameters by name. Positional
|
|
91
|
+
# optionals can only be filled left-to-right: once one is unspecified,
|
|
92
|
+
# passing any later :opt/:rest argument is an error (nonspecified_last_opt).
|
|
150
93
|
def run_method_internal(method, args, &block)
|
|
151
94
|
yoshinon = Yoshinon.lock if @use_yoshinon
|
|
152
95
|
params = method.parameters
|
|
153
|
-
return method[] if params.empty?
|
|
96
|
+
return method[&block] if params.empty?
|
|
154
97
|
|
|
155
98
|
params_array = []
|
|
156
99
|
params_hash = {}
|
|
@@ -160,7 +103,8 @@ module Yuuki
|
|
|
160
103
|
case type
|
|
161
104
|
when :req
|
|
162
105
|
unless args.key?(name)
|
|
163
|
-
raise Yuuki::Error,
|
|
106
|
+
raise Yuuki::Error,
|
|
107
|
+
"A required argument '#{name}' was not found on running #{method.owner}::#{method.name}"
|
|
164
108
|
end
|
|
165
109
|
|
|
166
110
|
params_array << args[name]
|
|
@@ -205,10 +149,11 @@ module Yuuki
|
|
|
205
149
|
params_hash[name] = args[name]
|
|
206
150
|
end
|
|
207
151
|
when :block
|
|
208
|
-
params_block = args[name]
|
|
152
|
+
params_block = args[name] if args.key?(name)
|
|
209
153
|
end
|
|
210
154
|
end
|
|
211
|
-
|
|
155
|
+
# fall back to the block passed to run unless one was explicitly given via args
|
|
156
|
+
params_block = block unless params.any? { |type, name| type == :block && args.key?(name) }
|
|
212
157
|
params_hash.empty? ? method[*params_array, ¶ms_block] : method[*params_array, **params_hash, ¶ms_block]
|
|
213
158
|
ensure
|
|
214
159
|
yoshinon&.unlock
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yoshinon'
|
|
4
|
+
require 'yuuki/caller'
|
|
5
|
+
require 'yuuki/runner'
|
|
6
|
+
|
|
7
|
+
module Yuuki
|
|
8
|
+
class PeriodicCaller < Caller
|
|
9
|
+
attr_reader :first_run, :current_time
|
|
10
|
+
|
|
11
|
+
def initialize(*instances, use_yoshinon: true)
|
|
12
|
+
super
|
|
13
|
+
@first_run = true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def on_error(&block)
|
|
17
|
+
@on_error = block
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def start(gmtoff = Time.now.gmtoff, **args, &block)
|
|
21
|
+
last_time = nil
|
|
22
|
+
loop do
|
|
23
|
+
@current_time = Time.now.to_f
|
|
24
|
+
begin
|
|
25
|
+
selector = proc do |_method, meta|
|
|
26
|
+
next true if @first_run && meta[:first_run]
|
|
27
|
+
next false unless meta[:periodic]
|
|
28
|
+
next false unless last_time
|
|
29
|
+
|
|
30
|
+
# gmtoff shifts epoch seconds into local time so that intervals
|
|
31
|
+
# align with local clock boundaries (e.g. 86400 fires at local midnight).
|
|
32
|
+
# Fires when a multiple of the interval lies in (last_time, current_time].
|
|
33
|
+
c = @current_time + gmtoff
|
|
34
|
+
l = last_time + gmtoff
|
|
35
|
+
(l.div(meta[:periodic]) + 1) * meta[:periodic] <= c
|
|
36
|
+
end
|
|
37
|
+
run_select(selector, **args, &block)
|
|
38
|
+
rescue StandardError
|
|
39
|
+
@on_error ? @on_error[$!] : raise
|
|
40
|
+
end
|
|
41
|
+
@first_run = false
|
|
42
|
+
|
|
43
|
+
last_time = @current_time
|
|
44
|
+
# wake at the next wall-clock second; the boundary check above makes
|
|
45
|
+
# ticks drift-free, but intervals below 1 second cannot be honored
|
|
46
|
+
sleep_duration = (@current_time + 1).floor - Time.now.to_f
|
|
47
|
+
sleep(sleep_duration) if sleep_duration > 0
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
data/lib/yuuki/runner.rb
CHANGED
|
@@ -1,63 +1,77 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Yuuki
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
class Runner
|
|
5
|
+
attr_reader :yuuki
|
|
6
|
+
|
|
7
|
+
# Decorator-style DSL (`on` / `priority` / `thread` / `periodic` / `first_run`)
|
|
8
|
+
# pushes entries here; the next instance-method definition consumes them
|
|
9
|
+
# (see method_added). Both this and yuuki_methods live in per-class ivars.
|
|
10
|
+
def self.accumulated_decorators
|
|
11
|
+
@accumulated_decorators ||= []
|
|
9
12
|
end
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
# @param [Array<Symbol>] methods method names
|
|
13
|
-
def add(*methods)
|
|
14
|
+
def self.yuuki_methods
|
|
14
15
|
@yuuki_methods ||= {}
|
|
15
|
-
methods.each do |method|
|
|
16
|
-
@yuuki_methods[method] ||= {}
|
|
17
|
-
@yuuki_methods[method][:enabled] = true
|
|
18
|
-
end
|
|
19
16
|
end
|
|
20
17
|
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
end
|
|
18
|
+
# yuuki_methods is a per-class ivar, so copy it on inheritance to make
|
|
19
|
+
# runner methods defined in a superclass visible from the subclass.
|
|
20
|
+
# NOTE: methods added to the superclass after the subclass is defined are
|
|
21
|
+
# not reflected.
|
|
22
|
+
def self.inherited(subclass)
|
|
23
|
+
subclass.instance_variable_set(:@yuuki_methods, yuuki_methods.dup)
|
|
24
|
+
super
|
|
29
25
|
end
|
|
30
26
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
# @param [Array<Symbol>] tags tag names
|
|
34
|
-
def tag(method, *tags)
|
|
35
|
-
@yuuki_methods ||= {}
|
|
36
|
-
@yuuki_methods[method] ||= {}
|
|
37
|
-
@yuuki_methods[method][:tags] ||= Set.new
|
|
38
|
-
@yuuki_methods[method][:tags].merge(tags)
|
|
27
|
+
def self.on(tag)
|
|
28
|
+
accumulated_decorators << [:tag, tag]
|
|
39
29
|
end
|
|
40
30
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
# @param [Boolean] enabled
|
|
44
|
-
def thread(*methods, enabled: true)
|
|
45
|
-
@yuuki_methods ||= {}
|
|
46
|
-
methods.each do |method|
|
|
47
|
-
@yuuki_methods[method] ||= {}
|
|
48
|
-
@yuuki_methods[method][:thread] = enabled
|
|
49
|
-
end
|
|
31
|
+
def self.priority(priority = 0)
|
|
32
|
+
accumulated_decorators << [:priority, priority]
|
|
50
33
|
end
|
|
51
34
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
35
|
+
def self.thread(enabled: true)
|
|
36
|
+
accumulated_decorators << [:thread, enabled]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.periodic(interval)
|
|
40
|
+
accumulated_decorators << [:periodic, interval]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.first_run(enabled: true)
|
|
44
|
+
accumulated_decorators << [:first_run, enabled]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Registers the method as a runner method when decorators are pending or the
|
|
48
|
+
# name is `on_<tag>` (which implies the tag <tag>). Note this hook fires for
|
|
49
|
+
# ANY instance-method definition, including attr_* and private helpers, so
|
|
50
|
+
# pending decorators are consumed by whatever method comes next.
|
|
51
|
+
def self.method_added(method_name)
|
|
52
|
+
return if accumulated_decorators.empty? && !method_name.start_with?('on_')
|
|
53
|
+
|
|
54
|
+
options = {}
|
|
55
|
+
options[:tags] = [method_name[3..].to_sym] if method_name.start_with?('on_')
|
|
56
|
+
|
|
57
|
+
accumulated_decorators.each do |tag, elem|
|
|
58
|
+
if tag == :tag
|
|
59
|
+
options[:tags] ||= []
|
|
60
|
+
options[:tags] << elem
|
|
61
|
+
else
|
|
62
|
+
options[tag] = elem
|
|
63
|
+
end
|
|
60
64
|
end
|
|
65
|
+
accumulated_decorators.clear
|
|
66
|
+
|
|
67
|
+
yuuki_methods[method_name] = options
|
|
68
|
+
|
|
69
|
+
super
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def self.singleton_method_added(_method_name)
|
|
73
|
+
accumulated_decorators.clear
|
|
74
|
+
super
|
|
61
75
|
end
|
|
62
76
|
end
|
|
63
77
|
end
|
data/lib/yuuki/version.rb
CHANGED
data/lib/yuuki.rb
CHANGED
data/sig/yuuki/caller.rbs
CHANGED
|
@@ -1,25 +1,43 @@
|
|
|
1
|
-
# TypeProf 0.21.2
|
|
2
|
-
|
|
3
|
-
# Classes
|
|
4
1
|
module Yuuki
|
|
5
2
|
class Caller
|
|
6
|
-
|
|
3
|
+
# a runner method paired with its registered metadata
|
|
4
|
+
type runner_entry = [Method, meta]
|
|
5
|
+
|
|
6
|
+
type selector = ^(Method, meta) -> boolish
|
|
7
|
+
|
|
8
|
+
@instances: Set[Runner]
|
|
9
|
+
|
|
7
10
|
@threads: Array[Thread]
|
|
8
11
|
|
|
12
|
+
@use_yoshinon: bool
|
|
13
|
+
|
|
9
14
|
def self.require_dir: (String require_dir, ?recursive: bool) -> void
|
|
10
|
-
|
|
11
|
-
def
|
|
12
|
-
|
|
13
|
-
def
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def
|
|
18
|
-
|
|
15
|
+
|
|
16
|
+
def initialize: (*(Runner | Class) instances, ?use_yoshinon: bool) -> void
|
|
17
|
+
|
|
18
|
+
def add: (*(Runner | Class) instances) -> void
|
|
19
|
+
|
|
20
|
+
# NOTE: intentionally shadows Object#methods; returns [Method, meta] pairs
|
|
21
|
+
# sorted by priority (highest first)
|
|
22
|
+
def methods: () -> Array[runner_entry]
|
|
23
|
+
|
|
24
|
+
def run: (*Symbol tags, **untyped args) ?{ (?) -> untyped } -> void
|
|
25
|
+
|
|
26
|
+
def run_select: (selector selector, **untyped args) ?{ (?) -> untyped } -> void
|
|
27
|
+
|
|
28
|
+
def join: () -> void
|
|
29
|
+
|
|
30
|
+
def alive?: () -> bool
|
|
31
|
+
|
|
19
32
|
alias running? alive?
|
|
20
33
|
|
|
21
34
|
private
|
|
22
|
-
|
|
23
|
-
def
|
|
35
|
+
|
|
36
|
+
def run_internal: (Array[runner_entry] runners, Hash[Symbol, untyped] args) ?{ (?) -> untyped } -> void
|
|
37
|
+
|
|
38
|
+
# Maps the args hash onto the method's parameters by name. Positional
|
|
39
|
+
# optionals can only be filled left-to-right: once one is unspecified,
|
|
40
|
+
# passing any later :opt/:rest argument is an error (nonspecified_last_opt).
|
|
41
|
+
def run_method_internal: (Method method, Hash[Symbol, untyped] args) ?{ (?) -> untyped } -> untyped
|
|
24
42
|
end
|
|
25
43
|
end
|
data/sig/yuuki/error.rbs
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Yuuki
|
|
2
|
+
class PeriodicCaller < Caller
|
|
3
|
+
@on_error: (^(StandardError) -> untyped)?
|
|
4
|
+
|
|
5
|
+
# true until the first iteration of #run has finished
|
|
6
|
+
attr_reader first_run: bool
|
|
7
|
+
|
|
8
|
+
# epoch seconds of the current iteration; nil before #run is called
|
|
9
|
+
attr_reader current_time: Float?
|
|
10
|
+
|
|
11
|
+
def initialize: (*(Runner | Class) instances, ?use_yoshinon: bool) -> void
|
|
12
|
+
|
|
13
|
+
def on_error: () { (StandardError) -> untyped } -> void
|
|
14
|
+
|
|
15
|
+
# loops forever; only returns by raising (when no on_error callback is set)
|
|
16
|
+
def start: (?Integer gmtoff, **untyped args) ?{ (?) -> untyped } -> bot
|
|
17
|
+
end
|
|
18
|
+
end
|
data/sig/yuuki/runner.rbs
CHANGED
|
@@ -1,14 +1,44 @@
|
|
|
1
|
-
# TypeProf 0.21.2
|
|
2
|
-
|
|
3
|
-
# Classes
|
|
4
1
|
module Yuuki
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
2
|
+
class Runner
|
|
3
|
+
# a decorator entry: [:tag, Symbol] | [:priority, Integer] | [:thread, bool]
|
|
4
|
+
# | [:periodic, Numeric] | [:first_run, bool]
|
|
5
|
+
type decorator = [Symbol, untyped]
|
|
6
|
+
|
|
7
|
+
self.@accumulated_decorators: Array[decorator]
|
|
8
|
+
|
|
9
|
+
self.@yuuki_methods: Hash[Symbol, meta]
|
|
10
|
+
|
|
11
|
+
attr_reader yuuki: Caller?
|
|
12
|
+
|
|
13
|
+
# Decorator-style DSL (`on` / `priority` / `thread` / `periodic` / `first_run`)
|
|
14
|
+
# pushes entries here; the next instance-method definition consumes them
|
|
15
|
+
# (see method_added). Both this and yuuki_methods live in per-class ivars.
|
|
16
|
+
def self.accumulated_decorators: () -> Array[decorator]
|
|
17
|
+
|
|
18
|
+
def self.yuuki_methods: () -> Hash[Symbol, meta]
|
|
19
|
+
|
|
20
|
+
# yuuki_methods is a per-class ivar, so copy it on inheritance to make
|
|
21
|
+
# runner methods defined in a superclass visible from the subclass.
|
|
22
|
+
# NOTE: methods added to the superclass after the subclass is defined are
|
|
23
|
+
# not reflected.
|
|
24
|
+
def self.inherited: (singleton(Runner) subclass) -> void
|
|
25
|
+
|
|
26
|
+
def self.on: (Symbol tag) -> void
|
|
27
|
+
|
|
28
|
+
def self.priority: (?Integer priority) -> void
|
|
29
|
+
|
|
30
|
+
def self.thread: (?enabled: bool) -> void
|
|
31
|
+
|
|
32
|
+
def self.periodic: (Numeric interval) -> void
|
|
33
|
+
|
|
34
|
+
def self.first_run: (?enabled: bool) -> void
|
|
35
|
+
|
|
36
|
+
# Registers the method as a runner method when decorators are pending or the
|
|
37
|
+
# name is `on_<tag>` (which implies the tag <tag>). Note this hook fires for
|
|
38
|
+
# ANY instance-method definition, including attr_* and private helpers, so
|
|
39
|
+
# pending decorators are consumed by whatever method comes next.
|
|
40
|
+
def self.method_added: (Symbol method_name) -> void
|
|
41
|
+
|
|
42
|
+
def self.singleton_method_added: (Symbol _method_name) -> void
|
|
13
43
|
end
|
|
14
44
|
end
|
data/sig/yuuki/version.rbs
CHANGED
data/sig/yuuki.rbs
ADDED
data/yuuki.gemspec
CHANGED
|
@@ -18,15 +18,18 @@ Gem::Specification.new do |spec|
|
|
|
18
18
|
# Specify which files should be added to the gem when it is released.
|
|
19
19
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
20
20
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
21
|
-
`git ls-files -z`.split("\x0").reject {|f| f.match(%r{^(test|spec|features)/})}
|
|
21
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
22
22
|
end
|
|
23
23
|
spec.bindir = 'exe'
|
|
24
|
-
spec.executables = spec.files.grep(%r{^exe/}) {|f| File.basename(f)}
|
|
24
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
25
25
|
spec.require_paths = ['lib']
|
|
26
|
+
spec.required_ruby_version = '>= 3.0'
|
|
26
27
|
|
|
27
28
|
spec.add_dependency 'yoshinon'
|
|
28
29
|
spec.add_development_dependency 'bundler'
|
|
29
|
-
spec.add_development_dependency '
|
|
30
|
-
spec.add_development_dependency 'pry'
|
|
30
|
+
spec.add_development_dependency 'irb'
|
|
31
31
|
spec.add_development_dependency 'rake'
|
|
32
|
+
spec.add_development_dependency 'rbs'
|
|
33
|
+
spec.add_development_dependency 'rspec'
|
|
34
|
+
spec.add_development_dependency 'rubocop'
|
|
32
35
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: yuuki
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ishotihadus
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: yoshinon
|
|
@@ -38,7 +38,7 @@ dependencies:
|
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '0'
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
|
-
name:
|
|
41
|
+
name: irb
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
43
43
|
requirements:
|
|
44
44
|
- - ">="
|
|
@@ -52,7 +52,7 @@ dependencies:
|
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
53
|
version: '0'
|
|
54
54
|
- !ruby/object:Gem::Dependency
|
|
55
|
-
name:
|
|
55
|
+
name: rake
|
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
|
57
57
|
requirements:
|
|
58
58
|
- - ">="
|
|
@@ -66,7 +66,35 @@ dependencies:
|
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
67
|
version: '0'
|
|
68
68
|
- !ruby/object:Gem::Dependency
|
|
69
|
-
name:
|
|
69
|
+
name: rbs
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: rspec
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: rubocop
|
|
70
98
|
requirement: !ruby/object:Gem::Requirement
|
|
71
99
|
requirements:
|
|
72
100
|
- - ">="
|
|
@@ -86,8 +114,10 @@ executables: []
|
|
|
86
114
|
extensions: []
|
|
87
115
|
extra_rdoc_files: []
|
|
88
116
|
files:
|
|
89
|
-
- ".
|
|
117
|
+
- ".github/workflows/ci.yml"
|
|
90
118
|
- ".gitignore"
|
|
119
|
+
- ".rspec"
|
|
120
|
+
- ".rubocop.yml"
|
|
91
121
|
- Gemfile
|
|
92
122
|
- LICENSE.txt
|
|
93
123
|
- README.md
|
|
@@ -97,12 +127,13 @@ files:
|
|
|
97
127
|
- lib/yuuki.rb
|
|
98
128
|
- lib/yuuki/caller.rb
|
|
99
129
|
- lib/yuuki/error.rb
|
|
100
|
-
- lib/yuuki/
|
|
130
|
+
- lib/yuuki/periodic_caller.rb
|
|
101
131
|
- lib/yuuki/runner.rb
|
|
102
132
|
- lib/yuuki/version.rb
|
|
133
|
+
- sig/yuuki.rbs
|
|
103
134
|
- sig/yuuki/caller.rbs
|
|
104
135
|
- sig/yuuki/error.rbs
|
|
105
|
-
- sig/yuuki/
|
|
136
|
+
- sig/yuuki/periodic_caller.rbs
|
|
106
137
|
- sig/yuuki/runner.rbs
|
|
107
138
|
- sig/yuuki/version.rbs
|
|
108
139
|
- yuuki.gemspec
|
|
@@ -117,14 +148,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
117
148
|
requirements:
|
|
118
149
|
- - ">="
|
|
119
150
|
- !ruby/object:Gem::Version
|
|
120
|
-
version: '0'
|
|
151
|
+
version: '3.0'
|
|
121
152
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
122
153
|
requirements:
|
|
123
154
|
- - ">="
|
|
124
155
|
- !ruby/object:Gem::Version
|
|
125
156
|
version: '0'
|
|
126
157
|
requirements: []
|
|
127
|
-
rubygems_version:
|
|
158
|
+
rubygems_version: 4.0.10
|
|
128
159
|
specification_version: 4
|
|
129
160
|
summary: A caller / runner framework for Ruby
|
|
130
161
|
test_files: []
|
data/.DS_Store
DELETED
|
Binary file
|
data/lib/yuuki/periodic.rb
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'English'
|
|
4
|
-
require 'yuuki/caller'
|
|
5
|
-
require 'yuuki/runner'
|
|
6
|
-
require 'yoshinon'
|
|
7
|
-
|
|
8
|
-
module Yuuki
|
|
9
|
-
module Runner
|
|
10
|
-
# sets interval to the method
|
|
11
|
-
# @param [Array<Symbol>] methods method names
|
|
12
|
-
# @param [Integer] interval
|
|
13
|
-
def periodic(*methods, interval)
|
|
14
|
-
@yuuki_methods ||= {}
|
|
15
|
-
methods.each do |method|
|
|
16
|
-
@yuuki_methods[method] ||= {}
|
|
17
|
-
@yuuki_methods[method][:periodic] = interval
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
# sets whether the method run at the first time
|
|
22
|
-
# @param [Array<Symbol>] methods method names
|
|
23
|
-
# @param [Boolean] enabled
|
|
24
|
-
def first_run(*methods, enabled: true)
|
|
25
|
-
@yuuki_methods ||= {}
|
|
26
|
-
methods.each do |method|
|
|
27
|
-
@yuuki_methods[method] ||= {}
|
|
28
|
-
@yuuki_methods[method][:first_run] = enabled
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
module Yuuki
|
|
35
|
-
# @attr_reader [Boolean] first_run
|
|
36
|
-
# @attr_reader [Float] current_time
|
|
37
|
-
class PeriodicCaller < Caller
|
|
38
|
-
attr_reader :first_run, :current_time
|
|
39
|
-
|
|
40
|
-
def initialize(*instances, use_yoshinon: true)
|
|
41
|
-
super
|
|
42
|
-
@first_run = true
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# sets error callback
|
|
46
|
-
# @yield [error]
|
|
47
|
-
# @yieldparam [Exception] error
|
|
48
|
-
def on_error(&block)
|
|
49
|
-
@on_error = block
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# runs the periodic caller
|
|
53
|
-
# @param [Numeric] gmtoff GMT Offset
|
|
54
|
-
# @param [Object] args arguments
|
|
55
|
-
def run(gmtoff = Time.now.gmtoff, **args, &)
|
|
56
|
-
last_time = nil
|
|
57
|
-
loop do
|
|
58
|
-
@current_time = Time.now.to_f
|
|
59
|
-
begin
|
|
60
|
-
select_proc = proc do |_method, meta|
|
|
61
|
-
next true if @first_run && meta[:first_run]
|
|
62
|
-
next false unless meta[:periodic]
|
|
63
|
-
next false unless last_time
|
|
64
|
-
|
|
65
|
-
c = @current_time + gmtoff
|
|
66
|
-
l = last_time + gmtoff
|
|
67
|
-
(l.div(meta[:periodic]) + 1) * meta[:periodic] <= c
|
|
68
|
-
end
|
|
69
|
-
run_select(select_proc, **args, &)
|
|
70
|
-
rescue StandardError
|
|
71
|
-
@on_error ? @on_error[$ERROR_INFO] : raise
|
|
72
|
-
end
|
|
73
|
-
@first_run = false
|
|
74
|
-
|
|
75
|
-
last_time = @current_time
|
|
76
|
-
((@current_time + 1).floor - Time.now.to_f).tap { |e| sleep e if e > 0 }
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
end
|
data/sig/yuuki/periodic.rbs
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# TypeProf 0.21.2
|
|
2
|
-
|
|
3
|
-
# Classes
|
|
4
|
-
module Yuuki
|
|
5
|
-
module Runner
|
|
6
|
-
def periodic: (Symbol method, Integer interval) -> void
|
|
7
|
-
def first_run: (Symbol method, ?enabled: bool) -> void
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
class PeriodicCaller < Caller
|
|
11
|
-
@on_error: Proc
|
|
12
|
-
|
|
13
|
-
attr_reader first_run: bool
|
|
14
|
-
attr_reader current_time: Float
|
|
15
|
-
def initialize: (*untyped instances) -> void
|
|
16
|
-
def on_error: {(Exception) -> void} -> void
|
|
17
|
-
def run: (?Integer gmtoff, **untyped) -> void
|
|
18
|
-
end
|
|
19
|
-
end
|