ni 0.1.0 → 0.1.1
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/Gemfile.lock +189 -0
- data/lib/ni/action_chain.rb +140 -0
- data/lib/ni/context.rb +217 -0
- data/lib/ni/expressions_language_engine/processor.rb +4 -0
- data/lib/ni/expressions_language_engine/tokenizer.rb +4 -0
- data/lib/ni/flows/base.rb +7 -0
- data/lib/ni/flows/branch_interactor.rb +65 -0
- data/lib/ni/flows/inline_interactor.rb +23 -0
- data/lib/ni/flows/isolated_inline_interactor.rb +21 -0
- data/lib/ni/flows/utils/handle_wait.rb +22 -0
- data/lib/ni/flows/wait_for_condition.rb +128 -0
- data/lib/ni/help.rb +69 -0
- data/lib/ni/main.rb +319 -0
- data/lib/ni/params.rb +109 -0
- data/lib/ni/result.rb +15 -0
- data/lib/ni/storages/active_record_metadata_repository.rb +51 -0
- data/lib/ni/storages/default.rb +147 -0
- data/lib/ni/storages_config.rb +31 -0
- data/lib/ni/tools/timers.rb +24 -0
- data/lib/ni/version.rb +1 -1
- data/ni.gemspec +1 -3
- metadata +21 -2
data/lib/ni/params.rb
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
module Ni
|
|
2
|
+
module Params
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
class Mapper
|
|
6
|
+
CONFIG_MAP = {
|
|
7
|
+
String => :description,
|
|
8
|
+
Hash => :options,
|
|
9
|
+
Proc => :contract,
|
|
10
|
+
Symbol => :method_contract
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
def self.perform(args)
|
|
14
|
+
args.each_with_object({}) do |next_arg, acc|
|
|
15
|
+
option = CONFIG_MAP[next_arg.class]
|
|
16
|
+
|
|
17
|
+
fail 'unknown argument' unless option.present?
|
|
18
|
+
|
|
19
|
+
acc[option] = next_arg
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
included do
|
|
25
|
+
def allow_to_read?(action, name)
|
|
26
|
+
receive_contracts = select_contracts_for_action(self.class.receive_contracts, action)
|
|
27
|
+
mutate_contracts = select_contracts_for_action(self.class.mutate_contracts, action)
|
|
28
|
+
provide_contracts = select_contracts_for_action(self.class.provide_contracts, action)
|
|
29
|
+
|
|
30
|
+
return true if receive_contracts.blank? && mutate_contracts.blank? && provide_contracts.blank?
|
|
31
|
+
|
|
32
|
+
receive_contracts.keys.include?(name) ||
|
|
33
|
+
mutate_contracts.keys.include?(name)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def allow_to_write?(action, name)
|
|
37
|
+
receive_contracts = select_contracts_for_action(self.class.receive_contracts, action)
|
|
38
|
+
mutate_contracts = select_contracts_for_action(self.class.mutate_contracts, action)
|
|
39
|
+
provide_contracts = select_contracts_for_action(self.class.provide_contracts, action)
|
|
40
|
+
|
|
41
|
+
return true if receive_contracts.blank? && mutate_contracts.blank? && provide_contracts.blank?
|
|
42
|
+
|
|
43
|
+
provide_contracts.keys.include?(name) ||
|
|
44
|
+
mutate_contracts.keys.include?(name)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
protected
|
|
48
|
+
|
|
49
|
+
def ensure_received_params(action=DEFAULT_ACTION)
|
|
50
|
+
ensure_contracts self.class.receive_contracts, action
|
|
51
|
+
ensure_contracts self.class.mutate_contracts, action
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def ensure_provided_params(action=DEFAULT_ACTION)
|
|
55
|
+
ensure_contracts self.class.provide_contracts, action
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def ensure_contracts(contracts, action)
|
|
59
|
+
select_contracts_for_action(contracts, action).each do |param, contract|
|
|
60
|
+
value = context.raw_get(param)
|
|
61
|
+
|
|
62
|
+
if contract[:contract].present?
|
|
63
|
+
unless contract[:contract].call(value)
|
|
64
|
+
fail "Value of `#{param}` doesn't match to contract"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
if contract[:method_contract].present?
|
|
69
|
+
unless value.respond_to?(contract[:method_contract], true)
|
|
70
|
+
fail "Value of `#{param}` doesn't respond to contract method :#{contract[:method_contract]}"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
unless value.send(contract[:method_contract])
|
|
74
|
+
fail "Value of `#{param}` doesn't match to contract :#{contract[:method_contract]}"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def select_contracts_for_action(contracts, action)
|
|
81
|
+
(contracts || {}).select do |_, contract|
|
|
82
|
+
contract.dig(:options, :for).blank? || contract.dig(:options, :for) == action
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
module ClassMethods
|
|
88
|
+
attr_accessor :receive_contracts, :provide_contracts, :mutate_contracts
|
|
89
|
+
|
|
90
|
+
def receive(*args)
|
|
91
|
+
@receive_contracts ||= {}
|
|
92
|
+
name = args.shift
|
|
93
|
+
@receive_contracts[name] = Mapper.perform(args)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def provide(*args)
|
|
97
|
+
@provide_contracts ||= {}
|
|
98
|
+
name = args.shift
|
|
99
|
+
@provide_contracts[name] = Mapper.perform(args)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def mutate(*args)
|
|
103
|
+
@mutate_contracts ||= {}
|
|
104
|
+
name = args.shift
|
|
105
|
+
@mutate_contracts[name] = Mapper.perform(args)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
data/lib/ni/result.rb
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module Ni
|
|
2
|
+
module Storages
|
|
3
|
+
if defined?(ActiveRecord)
|
|
4
|
+
class ActiveRecordMetadataRepository < ActiveRecord::Base
|
|
5
|
+
self.table_name = "ni_metadata"
|
|
6
|
+
|
|
7
|
+
TIMER_KEY = 'timer'.freeze
|
|
8
|
+
|
|
9
|
+
def self.store(uid, key, data)
|
|
10
|
+
record = self.where(uid: uid, key: key).first_or_initialize
|
|
11
|
+
record.update(data: data.to_json)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.fetch(uid, key)
|
|
15
|
+
record = self.where(uid: uid, key: key).first
|
|
16
|
+
|
|
17
|
+
if record.present?
|
|
18
|
+
JSON.parse(record.data, symbolize_names: true)
|
|
19
|
+
else
|
|
20
|
+
{}
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.setup_timer!(timer_id, datetime, timer_klass_name, timer_action, system_uid)
|
|
25
|
+
data = [timer_klass_name, timer_action, system_uid].to_json
|
|
26
|
+
self.create!(uid: timer_id, key: TIMER_KEY, run_timer_at: datetime, data: data)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.clear_timer!(timer_id)
|
|
30
|
+
self.where(uid: timer_id, key: TIMER_KEY).delete_all
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.fetch_timers
|
|
34
|
+
self.where(key: TIMER_KEY).where("run_timer_at < ?", Time.now).map do |record|
|
|
35
|
+
[record.uid] + JSON.parse(record.data)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
else
|
|
40
|
+
class ActiveRecordMetadataRepository
|
|
41
|
+
def self.store(uid, key, data)
|
|
42
|
+
raise "ActiveRecord not found"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.fetch(uid, key)
|
|
46
|
+
raise "ActiveRecord not found"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
module Ni
|
|
2
|
+
module Storages
|
|
3
|
+
class Default
|
|
4
|
+
METADATA_REPOSITORY_KEY = 'store_state'
|
|
5
|
+
|
|
6
|
+
def initialize(context, metadata_repository_klass)
|
|
7
|
+
@context, @metadata_repository_klass = context, metadata_repository_klass
|
|
8
|
+
|
|
9
|
+
@storages_map = {}
|
|
10
|
+
|
|
11
|
+
setup_custom_storages
|
|
12
|
+
|
|
13
|
+
if defined?(ActiveRecord) && !skip_default_storages?
|
|
14
|
+
register_storage :active_record_record, {
|
|
15
|
+
match: ActiveRecord::Base,
|
|
16
|
+
store: -> (record) { record.save!; [record.class.name, record.id] },
|
|
17
|
+
fetch: -> (data) { data.first.constantize.find(data.last) }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
register_storage :active_record_collection, {
|
|
21
|
+
match: -> (value) do
|
|
22
|
+
value.respond_to?(:each) &&
|
|
23
|
+
value.all? { |element| element.is_a?(ActiveRecord::Base) } &&
|
|
24
|
+
value.all? { |element| element.class == value.first.class }
|
|
25
|
+
end,
|
|
26
|
+
store: -> (records) do
|
|
27
|
+
records.each(&:save!)
|
|
28
|
+
[records.first.class.name] + records.map(&:id)
|
|
29
|
+
end,
|
|
30
|
+
fetch: -> (data) do
|
|
31
|
+
klass = data.shift.constantize
|
|
32
|
+
klass.where(id: data)
|
|
33
|
+
end
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
unless skip_default_storages?
|
|
38
|
+
register_storage :true_value, {
|
|
39
|
+
match: TrueClass,
|
|
40
|
+
store: -> (value) { nil },
|
|
41
|
+
fetch: -> (data) { true }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
register_storage :false_value, {
|
|
45
|
+
match: FalseClass,
|
|
46
|
+
store: -> (value) { nil },
|
|
47
|
+
fetch: -> (data) { false }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
register_storage :string_value, {
|
|
51
|
+
match: String,
|
|
52
|
+
store: -> (value) { value },
|
|
53
|
+
fetch: -> (data) { data }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
register_storage :integer_value, {
|
|
57
|
+
match: Integer,
|
|
58
|
+
store: -> (value) { value },
|
|
59
|
+
fetch: -> (data) { data.to_i }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
register_storage :float_value, {
|
|
63
|
+
match: Float,
|
|
64
|
+
store: -> (value) { value },
|
|
65
|
+
fetch: -> (data) { data.to_f }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
register_storage :symbol_value, {
|
|
69
|
+
match: Symbol,
|
|
70
|
+
store: -> (value) { value },
|
|
71
|
+
fetch: -> (data) { data.to_sym }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
register_storage :nil_value, {
|
|
75
|
+
match: NilClass,
|
|
76
|
+
store: -> (value) { nil },
|
|
77
|
+
fetch: -> (data) { nil }
|
|
78
|
+
}
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def store
|
|
83
|
+
fetch_data = {}
|
|
84
|
+
|
|
85
|
+
@context.each do |name, value|
|
|
86
|
+
known_storage, config = @storages_map.find do |storage_name, cfg|
|
|
87
|
+
match = cfg[:match]
|
|
88
|
+
|
|
89
|
+
(match.is_a?(Class) && value.is_a?(match)) ||
|
|
90
|
+
(match.is_a?(String) && value.is_a?(match.constantize)) ||
|
|
91
|
+
(match.is_a?(Proc) && match.call(value))
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
if respond_to?(:"store_#{name}")
|
|
95
|
+
fetch_data[name] = { known_storage: nil, data: public_send(:"store_#{name}", value) }
|
|
96
|
+
elsif known_storage.present?
|
|
97
|
+
known_logic = config[:store]
|
|
98
|
+
|
|
99
|
+
if known_logic.is_a?(Proc)
|
|
100
|
+
fetch_data[name] = { known_storage: known_storage, data: known_logic.call(value) }
|
|
101
|
+
else
|
|
102
|
+
raise "Storage logic type is not supported"
|
|
103
|
+
end
|
|
104
|
+
else
|
|
105
|
+
raise "Logic for storing #{name} was not defined"
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
@metadata_repository_klass.store(@context.system_uid, METADATA_REPOSITORY_KEY, fetch_data)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def fetch
|
|
113
|
+
fetch_data = @metadata_repository_klass.fetch(@context.system_uid, METADATA_REPOSITORY_KEY)
|
|
114
|
+
|
|
115
|
+
fetch_data.each do |name, data|
|
|
116
|
+
next if @context.has_key?(name) # do not restore an existing value. E.g. it can be provided on process launch
|
|
117
|
+
|
|
118
|
+
if respond_to?(:"store_#{name}")
|
|
119
|
+
@context.raw_set(name, public_send(:"fetch_#{name}", data[:data]))
|
|
120
|
+
elsif data[:known_storage].present?
|
|
121
|
+
if @storages_map[data[:known_storage].to_sym].present?
|
|
122
|
+
@context.raw_set(name, @storages_map[data[:known_storage].to_sym][:fetch].call(data[:data]))
|
|
123
|
+
else
|
|
124
|
+
raise "Storage is not known"
|
|
125
|
+
end
|
|
126
|
+
else
|
|
127
|
+
raise "Doesn't know how to fetch #{name}"
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
private
|
|
133
|
+
|
|
134
|
+
def skip_default_storages?
|
|
135
|
+
false
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def setup_custom_storages
|
|
139
|
+
# can be defined for custom storages
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def register_storage(name, config)
|
|
143
|
+
@storages_map[name] ||= config
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Ni
|
|
2
|
+
module StoragesConfig
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
module ClassMethods
|
|
6
|
+
cattr_accessor :context_storage_klass, :metadata_repository_klass
|
|
7
|
+
|
|
8
|
+
def storage(klass)
|
|
9
|
+
self.context_storage_klass = klass
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def metadata_repository(klass)
|
|
13
|
+
self.metadata_repository_klass = klass
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def copy_storage_and_metadata_repository(interactor_klass)
|
|
17
|
+
unless self.context_storage_klass.present?
|
|
18
|
+
if interactor_klass.context_storage_klass.present?
|
|
19
|
+
storage interactor_klass.context_storage_klass
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
unless self.metadata_repository_klass.present?
|
|
24
|
+
if interactor_klass.metadata_repository_klass.present?
|
|
25
|
+
metadata_repository interactor_klass.metadata_repository_klass
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Ni::Tools
|
|
2
|
+
class Timers
|
|
3
|
+
def self.fetch_and_run(metadata_repository_klass, exceptions_logger=nil)
|
|
4
|
+
current_timers = metadata_repository_klass.fetch_timers
|
|
5
|
+
exceptions = []
|
|
6
|
+
|
|
7
|
+
current_timers.each do |data|
|
|
8
|
+
id, klass_name, action, system_uid = data
|
|
9
|
+
|
|
10
|
+
begin
|
|
11
|
+
klass_name.constantize.public_send(action, system_uid: system_uid)
|
|
12
|
+
rescue Exception => e
|
|
13
|
+
exceptions << e
|
|
14
|
+
ensure
|
|
15
|
+
metadata_repository_klass.clear_timer!(id)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
if exceptions_logger.present? && exceptions.present?
|
|
20
|
+
exceptions.each { |e| exceptions_logger.log(e) }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
data/lib/ni/version.rb
CHANGED
data/ni.gemspec
CHANGED
|
@@ -29,9 +29,7 @@ Gem::Specification.new do |spec|
|
|
|
29
29
|
|
|
30
30
|
# Specify which files should be added to the gem when it is released.
|
|
31
31
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
32
|
-
spec.files =
|
|
33
|
-
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
34
|
-
end
|
|
32
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|coverage)/}) }
|
|
35
33
|
spec.bindir = "exe"
|
|
36
34
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
37
35
|
spec.require_paths = ["lib"]
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ni
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- KnightWhosayNi
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2019-09-
|
|
11
|
+
date: 2019-09-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -64,12 +64,31 @@ files:
|
|
|
64
64
|
- ".travis.yml"
|
|
65
65
|
- CODE_OF_CONDUCT.md
|
|
66
66
|
- Gemfile
|
|
67
|
+
- Gemfile.lock
|
|
67
68
|
- LICENSE.txt
|
|
68
69
|
- README.md
|
|
69
70
|
- Rakefile
|
|
70
71
|
- bin/console
|
|
71
72
|
- bin/setup
|
|
72
73
|
- lib/ni.rb
|
|
74
|
+
- lib/ni/action_chain.rb
|
|
75
|
+
- lib/ni/context.rb
|
|
76
|
+
- lib/ni/expressions_language_engine/processor.rb
|
|
77
|
+
- lib/ni/expressions_language_engine/tokenizer.rb
|
|
78
|
+
- lib/ni/flows/base.rb
|
|
79
|
+
- lib/ni/flows/branch_interactor.rb
|
|
80
|
+
- lib/ni/flows/inline_interactor.rb
|
|
81
|
+
- lib/ni/flows/isolated_inline_interactor.rb
|
|
82
|
+
- lib/ni/flows/utils/handle_wait.rb
|
|
83
|
+
- lib/ni/flows/wait_for_condition.rb
|
|
84
|
+
- lib/ni/help.rb
|
|
85
|
+
- lib/ni/main.rb
|
|
86
|
+
- lib/ni/params.rb
|
|
87
|
+
- lib/ni/result.rb
|
|
88
|
+
- lib/ni/storages/active_record_metadata_repository.rb
|
|
89
|
+
- lib/ni/storages/default.rb
|
|
90
|
+
- lib/ni/storages_config.rb
|
|
91
|
+
- lib/ni/tools/timers.rb
|
|
73
92
|
- lib/ni/version.rb
|
|
74
93
|
- ni.gemspec
|
|
75
94
|
homepage: https://github.com/trueknightwhosayni/ni
|