edgycircle_kommando 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/db/migrations/1.down.sql +2 -0
- data/db/migrations/1.up.sql +12 -0
- data/lib/kommando.rb +12 -0
- data/lib/kommando/active_record.rb +9 -0
- data/lib/kommando/command.rb +27 -0
- data/lib/kommando/command_plugins/auto_schedule.rb +15 -0
- data/lib/kommando/command_plugins/base.rb +17 -0
- data/lib/kommando/command_plugins/execute.rb +79 -0
- data/lib/kommando/command_plugins/schedule.rb +105 -0
- data/lib/kommando/command_plugins/validate.rb +52 -0
- data/lib/kommando/command_result.rb +68 -0
- data/lib/kommando/scheduled_command_adapters/active_record.rb +52 -0
- data/lib/kommando/scheduled_command_runner.rb +25 -0
- data/lib/kommando/scheduled_command_worker.rb +23 -0
- data/lib/kommando/version.rb +3 -0
- metadata +142 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e2962518e6deb43c8cd51657f311ebbd96caf84d0e04cfb7505c7c427fddec0b
|
4
|
+
data.tar.gz: 8cdfe6722fb04875ee8fc3e0fd1240d8c6edbae8dbddb992cc3a9fa89c4c6166
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 895e1f7f17a43f5b9ec5d8fe05a71a09d3b024c55c639f4fcd4c0916597502f0ff23caf352f9001c6d3e45b6b959f134fa590576e83a24383dfc2cf53f9f6434
|
7
|
+
data.tar.gz: cdb250ff046a059b5aaf59f7fcc2384ff8174e9fbdde943826d09280403f41f4bbd9f5ec7127158b5dec014f1128dfd01c7e7e93afcd11ef4004bc4e602722f2
|
@@ -0,0 +1,12 @@
|
|
1
|
+
CREATE TABLE kommando_scheduled_commands (
|
2
|
+
id uuid NOT NULL,
|
3
|
+
name character varying NOT NULL,
|
4
|
+
parameters json NOT NULL,
|
5
|
+
handle_at timestamp without time zone NOT NULL,
|
6
|
+
failures json[] NOT NULL,
|
7
|
+
wait_for_command_ids uuid[] DEFAULT '{}'::uuid[],
|
8
|
+
|
9
|
+
PRIMARY KEY (id)
|
10
|
+
);
|
11
|
+
|
12
|
+
CREATE INDEX index_kommando_scheduled_commands_on_handle_at ON kommando_scheduled_commands USING btree (handle_at);
|
data/lib/kommando.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require_relative './kommando/command'
|
2
|
+
require_relative './kommando/command_result'
|
3
|
+
require_relative './kommando/scheduled_command_runner'
|
4
|
+
require_relative './kommando/scheduled_command_worker'
|
5
|
+
require_relative './kommando/command_plugins/base'
|
6
|
+
require_relative './kommando/command_plugins/execute'
|
7
|
+
require_relative './kommando/command_plugins/schedule'
|
8
|
+
require_relative './kommando/command_plugins/validate'
|
9
|
+
require_relative './kommando/command_plugins/auto_schedule'
|
10
|
+
|
11
|
+
module Kommando
|
12
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative './command_plugins/base'
|
2
|
+
require_relative './command_plugins/execute'
|
3
|
+
require_relative './command_plugins/schedule'
|
4
|
+
|
5
|
+
module Kommando
|
6
|
+
class Command
|
7
|
+
class MissingDependencyError < StandardError; end
|
8
|
+
|
9
|
+
class MissingParameterError < StandardError; end
|
10
|
+
|
11
|
+
class ReservedParameterError < StandardError; end
|
12
|
+
|
13
|
+
class UnknownCommandError < StandardError; end
|
14
|
+
|
15
|
+
@options = {}
|
16
|
+
|
17
|
+
def self.plugin(plugin, *args)
|
18
|
+
include plugin::InstanceMethods if defined?(plugin::InstanceMethods)
|
19
|
+
extend plugin::ClassMethods if defined?(plugin::ClassMethods)
|
20
|
+
plugin.configure(self, *args)
|
21
|
+
end
|
22
|
+
|
23
|
+
plugin CommandPlugins::Base
|
24
|
+
plugin CommandPlugins::Execute
|
25
|
+
plugin CommandPlugins::Schedule
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Kommando
|
2
|
+
module CommandPlugins
|
3
|
+
module AutoSchedule
|
4
|
+
def self.configure(command_klass, options)
|
5
|
+
command_klass.options[:default_handle_at] = options.fetch(:handle_at)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def schedule(dependencies, parameters)
|
10
|
+
super(dependencies, { handle_at: options[:default_handle_at].call }.merge(parameters))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Kommando
|
2
|
+
module CommandPlugins
|
3
|
+
module Base
|
4
|
+
def self.configure(command_klass, *args)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
attr_reader :options
|
9
|
+
|
10
|
+
def inherited(subclass)
|
11
|
+
super
|
12
|
+
subclass.instance_variable_set(:@options, options.dup)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
module Kommando
|
4
|
+
module CommandPlugins
|
5
|
+
module Execute
|
6
|
+
def self.configure(command_klass, *args)
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def execute(dependencies, parameters)
|
11
|
+
context = {
|
12
|
+
dependencies: dependencies,
|
13
|
+
parameters: parameters,
|
14
|
+
instance: new,
|
15
|
+
}
|
16
|
+
|
17
|
+
context = _before_execute(context)
|
18
|
+
return _halt_with_failure(context) if context.key?(:halt)
|
19
|
+
|
20
|
+
context = _execute(context)
|
21
|
+
return _halt_with_failure(context) if context.key?(:halt)
|
22
|
+
|
23
|
+
context = _after_execute(context)
|
24
|
+
return _halt_with_failure(context) if context.key?(:halt)
|
25
|
+
|
26
|
+
_execute_context_to_result(context)
|
27
|
+
end
|
28
|
+
|
29
|
+
def _before_execute(context)
|
30
|
+
unless context[:parameters].key?(:command_id)
|
31
|
+
context[:parameters] = context[:parameters].merge({ command_id: SecureRandom.uuid })
|
32
|
+
end
|
33
|
+
|
34
|
+
context
|
35
|
+
end
|
36
|
+
|
37
|
+
def _execute(context)
|
38
|
+
context.merge(execute_return_value: context[:instance].handle(context[:dependencies], context[:parameters]))
|
39
|
+
rescue StandardError => error
|
40
|
+
context.merge(halt: { error: :unhandled_exception, data: error })
|
41
|
+
end
|
42
|
+
|
43
|
+
def _after_execute(context)
|
44
|
+
case context[:execute_return_value]
|
45
|
+
when CommandResult::Failure
|
46
|
+
context.merge(halt: context[:execute_return_value])
|
47
|
+
else
|
48
|
+
context
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def _execute_context_to_result(context)
|
53
|
+
CommandResult.success(_execute_context_to_data({}, context))
|
54
|
+
end
|
55
|
+
|
56
|
+
def _halt_with_failure(context)
|
57
|
+
case context[:halt]
|
58
|
+
when CommandResult::Failure
|
59
|
+
context[:halt]
|
60
|
+
else
|
61
|
+
CommandResult.failure(_execute_context_to_data({}, context).merge({ error: context[:halt][:error], details: context[:halt][:data] }))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def _execute_context_to_data(data, context)
|
66
|
+
data.merge(context.slice(:parameters)).merge(command: name)
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
def reserved_parameter_keys
|
71
|
+
[:command_id]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
module InstanceMethods
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
module Kommando
|
4
|
+
module CommandPlugins
|
5
|
+
module Schedule
|
6
|
+
def self.configure(command_klass, *args)
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def schedule(dependencies, parameters)
|
11
|
+
unless dependencies.key?(:schedule_adapter)
|
12
|
+
raise Command::MissingDependencyError, 'You need to provide the `:schedule_adapter` dependency'
|
13
|
+
end
|
14
|
+
|
15
|
+
unless parameters.key?(:handle_at)
|
16
|
+
raise Command::MissingParameterError, 'You need to provide the `:handle_at` parameter'
|
17
|
+
end
|
18
|
+
|
19
|
+
context = {
|
20
|
+
parameters: parameters,
|
21
|
+
schedule_adapter: dependencies[:schedule_adapter],
|
22
|
+
handle_at: parameters[:handle_at].getutc,
|
23
|
+
}
|
24
|
+
|
25
|
+
context = _before_schedule(context)
|
26
|
+
return _halt_schedule_with_failure(context) if context.key?(:halt)
|
27
|
+
|
28
|
+
context = _schedule(context)
|
29
|
+
return _halt_schedule_with_failure(context) if context.key?(:halt)
|
30
|
+
|
31
|
+
context = _after_schedule(context)
|
32
|
+
return _halt_schedule_with_failure(context) if context.key?(:halt)
|
33
|
+
|
34
|
+
_schedule_context_to_result(context)
|
35
|
+
end
|
36
|
+
|
37
|
+
def _before_schedule(context)
|
38
|
+
unless context[:parameters].key?(:command_id)
|
39
|
+
context[:parameters] = context[:parameters].merge({ command_id: SecureRandom.uuid })
|
40
|
+
end
|
41
|
+
|
42
|
+
context
|
43
|
+
end
|
44
|
+
|
45
|
+
def _schedule(context)
|
46
|
+
context.merge(schedule_return_value: context[:schedule_adapter].schedule!(name, context[:parameters], context[:handle_at]))
|
47
|
+
end
|
48
|
+
|
49
|
+
def _after_schedule(context)
|
50
|
+
context
|
51
|
+
end
|
52
|
+
|
53
|
+
def _before_execute(context)
|
54
|
+
context[:instance].scheduled_command_results = []
|
55
|
+
super(context)
|
56
|
+
end
|
57
|
+
|
58
|
+
def _after_execute(context)
|
59
|
+
results = context[:instance].scheduled_command_results
|
60
|
+
|
61
|
+
if failed_result = results.find(&:error?)
|
62
|
+
super(context.merge(halt: CommandResult.failure({ command: name, parameters: context[:parameters] }.merge({ error: :scheduling_error, details: failed_result.error }))))
|
63
|
+
else
|
64
|
+
super(context)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def _schedule_context_to_result(context)
|
69
|
+
CommandResult.success(_schedule_context_to_data({}, context))
|
70
|
+
end
|
71
|
+
|
72
|
+
def _execute_context_to_data(data, context)
|
73
|
+
results = context[:instance].scheduled_command_results
|
74
|
+
super(data.merge(scheduled_commands: results.map(&:value)), context)
|
75
|
+
end
|
76
|
+
|
77
|
+
def _schedule_context_to_data(data, context)
|
78
|
+
data.merge(context.slice(:parameters)).merge(command: name)
|
79
|
+
end
|
80
|
+
|
81
|
+
def _halt_schedule_with_failure(context)
|
82
|
+
case context[:halt]
|
83
|
+
when CommandResult::Failure
|
84
|
+
context[:halt]
|
85
|
+
else
|
86
|
+
CommandResult.failure(_schedule_context_to_data({}, context).merge({ error: context[:halt][:error], details: context[:halt][:data] }))
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
def reserved_parameter_keys
|
92
|
+
super.concat([:handle_at, :wait_for_command_ids, :command_id]).uniq
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
module InstanceMethods
|
97
|
+
attr_accessor :scheduled_command_results
|
98
|
+
|
99
|
+
def scheduled(result)
|
100
|
+
@scheduled_command_results.push(result)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Kommando
|
2
|
+
module CommandPlugins
|
3
|
+
module Validate
|
4
|
+
def self.configure(command_klass, *args)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def _before_execute(context)
|
9
|
+
if const_defined?('Schema')
|
10
|
+
schema_keys = const_get('Schema').schema.key_map.map(&:name).map(&:to_sym)
|
11
|
+
|
12
|
+
if forbidden_key = reserved_parameter_keys.find { |key| schema_keys.include?(key) }
|
13
|
+
raise Command::ReservedParameterError, "The `#{forbidden_key}` parameter is reserved and cannot be used in command schema"
|
14
|
+
end
|
15
|
+
|
16
|
+
reserved_parameters = context[:parameters].slice(*reserved_parameter_keys)
|
17
|
+
validation_result = const_get('Schema').new.call(context[:parameters])
|
18
|
+
|
19
|
+
if validation_result.success?
|
20
|
+
super(context.merge(parameters: validation_result.to_h.merge(reserved_parameters)))
|
21
|
+
else
|
22
|
+
super(context.merge(halt: { error: :schema_error, data: validation_result.errors.to_h }))
|
23
|
+
end
|
24
|
+
else
|
25
|
+
super(context)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def _before_schedule(context)
|
30
|
+
if const_defined?('Schema')
|
31
|
+
schema_keys = const_get('Schema').schema.key_map.map(&:name).map(&:to_sym)
|
32
|
+
|
33
|
+
if forbidden_key = reserved_parameter_keys.find { |key| schema_keys.include?(key) }
|
34
|
+
raise Command::ReservedParameterError, "The `#{forbidden_key}` parameter is reserved and cannot be used in command schema"
|
35
|
+
end
|
36
|
+
|
37
|
+
reserved_parameters = context[:parameters].slice(*reserved_parameter_keys)
|
38
|
+
validation_result = const_get('Schema').new.call(context[:parameters])
|
39
|
+
|
40
|
+
if validation_result.success?
|
41
|
+
super(context.merge(parameters: validation_result.to_h.merge(reserved_parameters)))
|
42
|
+
else
|
43
|
+
super(context.merge(halt: { error: :schema_error, data: validation_result.errors.to_h }))
|
44
|
+
end
|
45
|
+
else
|
46
|
+
super(context)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Kommando
|
2
|
+
class CommandResult
|
3
|
+
class NonExistentError < StandardError; end
|
4
|
+
|
5
|
+
class NonExistentValue < StandardError; end
|
6
|
+
|
7
|
+
def self.success(value)
|
8
|
+
Success.new(value)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.failure(error)
|
12
|
+
Failure.new(error)
|
13
|
+
end
|
14
|
+
|
15
|
+
class Success
|
16
|
+
attr_reader :value
|
17
|
+
|
18
|
+
def initialize(value)
|
19
|
+
raise ArgumentError, 'missing `:command` key' unless value.key?(:command)
|
20
|
+
raise ArgumentError, 'missing `:parameters` key' unless value.key?(:parameters)
|
21
|
+
@value = value
|
22
|
+
end
|
23
|
+
|
24
|
+
def error
|
25
|
+
raise NonExistentError, 'Success results do not have errors'
|
26
|
+
end
|
27
|
+
|
28
|
+
def success?
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
def error?
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
def deconstruct_keys(_)
|
37
|
+
{ value: @value[:command] }.merge(@value)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Failure
|
42
|
+
attr_reader :error
|
43
|
+
|
44
|
+
def initialize(error)
|
45
|
+
raise ArgumentError, 'missing `:error` key' unless error.key?(:error)
|
46
|
+
raise ArgumentError, 'missing `:command` key' unless error.key?(:command)
|
47
|
+
raise ArgumentError, 'missing `:parameters` key' unless error.key?(:parameters)
|
48
|
+
@error = error
|
49
|
+
end
|
50
|
+
|
51
|
+
def value
|
52
|
+
raise NonExistentValue, 'Failure results do not have values'
|
53
|
+
end
|
54
|
+
|
55
|
+
def success?
|
56
|
+
false
|
57
|
+
end
|
58
|
+
|
59
|
+
def error?
|
60
|
+
true
|
61
|
+
end
|
62
|
+
|
63
|
+
def deconstruct_keys(_)
|
64
|
+
{ error: error }.merge(@error)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module Kommando
|
4
|
+
module ScheduledCommandAdapters
|
5
|
+
class ActiveRecord < ::ActiveRecord::Base
|
6
|
+
self.table_name = 'kommando_scheduled_commands'
|
7
|
+
|
8
|
+
def self.schedule!(command, parameters, handle_at)
|
9
|
+
create!({
|
10
|
+
id: parameters.fetch(:command_id),
|
11
|
+
name: command,
|
12
|
+
parameters: parameters,
|
13
|
+
handle_at: handle_at,
|
14
|
+
failures: [],
|
15
|
+
wait_for_command_ids: parameters.fetch(:wait_for_command_ids, []),
|
16
|
+
})
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.fetch!(&block)
|
20
|
+
transaction do
|
21
|
+
record = lock('FOR UPDATE SKIP LOCKED').
|
22
|
+
where('TIMEZONE(\'UTC\', NOW()) >= handle_at').
|
23
|
+
where.not("wait_for_command_ids && (SELECT array_agg(id) FROM #{table_name})").
|
24
|
+
order(handle_at: :asc).
|
25
|
+
limit(1).
|
26
|
+
first
|
27
|
+
|
28
|
+
if record
|
29
|
+
result = block.call(record.name, record.parameters)
|
30
|
+
|
31
|
+
if result.success?
|
32
|
+
record.destroy
|
33
|
+
else
|
34
|
+
record.update!({
|
35
|
+
failures: record.failures.append(result.error),
|
36
|
+
handle_at: (record.handle_at + 5.minutes).getutc,
|
37
|
+
})
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def parameters
|
44
|
+
@parameters ||= super.deep_symbolize_keys!
|
45
|
+
end
|
46
|
+
|
47
|
+
def failures
|
48
|
+
@failures ||= super.map(&:deep_symbolize_keys!)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Kommando
|
2
|
+
class ScheduledCommandRunner
|
3
|
+
def initialize(coordinator, adapter, dependencies)
|
4
|
+
@coordinator = coordinator
|
5
|
+
@adapter = adapter
|
6
|
+
@dependencies = dependencies
|
7
|
+
end
|
8
|
+
|
9
|
+
def call
|
10
|
+
fetched_command = false
|
11
|
+
|
12
|
+
@adapter.fetch! do |command_name, parameters|
|
13
|
+
unless self.class.const_defined?(command_name)
|
14
|
+
raise Command::UnknownCommandError, "Unknown command `#{command_name}`"
|
15
|
+
end
|
16
|
+
|
17
|
+
fetched_command = true
|
18
|
+
|
19
|
+
self.class.const_get(command_name).execute(@dependencies, parameters)
|
20
|
+
end
|
21
|
+
|
22
|
+
@coordinator.schedule_next_run(fetched_command ? 0 : 5)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Kommando
|
2
|
+
class ScheduledCommandWorker
|
3
|
+
def initialize(adapter, dependencies, number_of_threads)
|
4
|
+
@adapter = adapter
|
5
|
+
@dependencies = dependencies
|
6
|
+
@number_of_threads = number_of_threads
|
7
|
+
end
|
8
|
+
|
9
|
+
def start
|
10
|
+
@pool = Concurrent::FixedThreadPool.new(@number_of_threads)
|
11
|
+
@number_of_threads.times { schedule_next_run(0) }
|
12
|
+
@pool.wait_for_termination
|
13
|
+
end
|
14
|
+
|
15
|
+
def stop
|
16
|
+
@pool.shutdown
|
17
|
+
end
|
18
|
+
|
19
|
+
def schedule_next_run(delay)
|
20
|
+
Concurrent::ScheduledTask.execute(delay, { executor: @pool }, &ScheduledCommandRunner.new(self, @adapter, @dependencies).method(:call))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: edgycircle_kommando
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- David Strauß
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 1970-01-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: pg
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: dry-validation
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rails
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '6.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '6.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '12.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '12.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: minitest
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '5.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '5.0'
|
97
|
+
description: Command architecture building blocks.
|
98
|
+
email:
|
99
|
+
- david.strauss@edgycircle.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- db/migrations/1.down.sql
|
105
|
+
- db/migrations/1.up.sql
|
106
|
+
- lib/kommando.rb
|
107
|
+
- lib/kommando/active_record.rb
|
108
|
+
- lib/kommando/command.rb
|
109
|
+
- lib/kommando/command_plugins/auto_schedule.rb
|
110
|
+
- lib/kommando/command_plugins/base.rb
|
111
|
+
- lib/kommando/command_plugins/execute.rb
|
112
|
+
- lib/kommando/command_plugins/schedule.rb
|
113
|
+
- lib/kommando/command_plugins/validate.rb
|
114
|
+
- lib/kommando/command_result.rb
|
115
|
+
- lib/kommando/scheduled_command_adapters/active_record.rb
|
116
|
+
- lib/kommando/scheduled_command_runner.rb
|
117
|
+
- lib/kommando/scheduled_command_worker.rb
|
118
|
+
- lib/kommando/version.rb
|
119
|
+
homepage: https://github.com/edgycircle/kommando
|
120
|
+
licenses:
|
121
|
+
- Nonstandard
|
122
|
+
metadata: {}
|
123
|
+
post_install_message:
|
124
|
+
rdoc_options: []
|
125
|
+
require_paths:
|
126
|
+
- lib
|
127
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 2.7.0
|
132
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - ">="
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '0'
|
137
|
+
requirements: []
|
138
|
+
rubygems_version: 3.1.2
|
139
|
+
signing_key:
|
140
|
+
specification_version: 4
|
141
|
+
summary: ''
|
142
|
+
test_files: []
|