kanal 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 45f85bfae55ce0c37d296113ba2af2933667fd3fd7ec9e181ff12dd17105b524
4
- data.tar.gz: 9f160009467a4cbf0412d266ae693b279064286ca91ec53e5ad55bb93582d31a
3
+ metadata.gz: 36d8d91a78e9f227b53d4b8f2221fed9890670c327477d4ce1e2506e70e7ac3d
4
+ data.tar.gz: 66bb506e933b59e5de10595a5f9a08e5225048d71d75d9c605a3a60e7fc2bf45
5
5
  SHA512:
6
- metadata.gz: f4275c770b3607a4617e5413a4634e2436fcb245af8dd3c6d3c5a86b39b64a70020f87ddb4c23323b9fcf3b640fd45bf572992edee3041bb5b918d6989fc79ab
7
- data.tar.gz: 9c11421ecd4b230ab8d994b17a016b9e9649c9820ae9ad04503c8bbfa77798fd323fda5249c47cc6bd712d19493a7373d68dbb78fe559f08f53d4a36194776d3
6
+ metadata.gz: 2baebfec99a7f2b973a37b07f681743cb6778856012a2530bfc5421a4d9eea6d7be1882881dc777e78fd660374c660e0d51a3f9bede04a1b2b0c47b050eebf52
7
+ data.tar.gz: b904037d8384582b2802ff714df534451642a3811981257619520584e486beaed43b9a22ee76a3ef81344921b9829ae8bc9950c7a26553b891f318b19ec29d13
data/CHANGELOG.md CHANGED
@@ -1,6 +1,11 @@
1
1
  ## [Unreleased]
2
2
 
3
- ## [0.3.0]
3
+ ## [0.4.0] 2023-03-10
4
+ - Added logging feature to Kanal, for a time being - just stdout
5
+ - Error response, now Kanal Router accepts error response block, when things go haywire inside constructing output
6
+ - Async response available, now developers can utilize respond_async(&block) which will be executed in separate thread
7
+
8
+ ## [0.3.0] - 2023-01-10
4
9
  - Kanal::Core::Core.get_plugin method added to get plugins for additional configuration by other plugins or developer code, if needed
5
10
  - Kanal::Core::Plugins::Plugin.rake_tasks method introduced, for plugins to have their own rake tasks that can be merged inside
6
11
  some kind of parent rake tasks, whether it's users Rakefile or kanal framework/interface or something Rakefile
data/kanal.gemspec CHANGED
@@ -36,4 +36,5 @@ Gem::Specification.new do |spec|
36
36
 
37
37
  # For more information and examples about making a new gem, check out our
38
38
  # guide at: https://bundler.io/guides/creating_gem.html
39
+ spec.metadata["rubygems_mfa_required"] = "true"
39
40
  end
@@ -1,15 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../logger/logging"
4
+
1
5
  module Kanal
2
6
  module Core
3
7
  module Conditions
4
8
  # Base class for conditions
5
9
  # with this class you can
6
10
  class Condition
11
+ include Logging
12
+
7
13
  attr_reader :name
8
14
 
9
15
  def initialize(name, with_argument: false, &met_block)
10
16
  @name = name
11
17
 
12
- raise "Cannot create condition without block" unless met_block
18
+ unless met_block
19
+ logger.fatal "Attempted to create condition #{name} without block"
20
+
21
+ raise "Cannot create condition without block"
22
+ end
13
23
 
14
24
  @with_argument = with_argument
15
25
 
@@ -1,9 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../logger/logging"
4
+
1
5
  module Kanal
2
6
  module Core
3
7
  module Conditions
4
8
  # This class helps creating conditions in dsl way,
5
9
  # with using helper methods
6
10
  class ConditionCreator
11
+ include Logging
12
+
7
13
  def initialize(name)
8
14
  @name = name
9
15
  @met_block = nil
@@ -11,10 +17,23 @@ module Kanal
11
17
  end
12
18
 
13
19
  def create(&block)
20
+ logger.info "Attempting to create condition '#{@name}'"
21
+
14
22
  instance_eval(&block)
15
23
 
16
- raise "Please provide name for condition" unless @name
17
- raise "Please provide met? block for condition #{@name}" unless @met_block
24
+ unless @name
25
+ logger.fatal "Attempted to create condition without name"
26
+
27
+ raise "Please provide name for condition"
28
+ end
29
+
30
+ unless @met_block
31
+ logger.fatal "Attempted to create condition without met block"
32
+
33
+ raise "Please provide met? block for condition #{@name}"
34
+ end
35
+
36
+ logger.info "Creating condition '#{@name}'"
18
37
 
19
38
  Condition.new @name, with_argument: @with_argument, &@met_block
20
39
  end
@@ -1,5 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "./condition"
2
4
  require_relative "./condition_creator"
5
+ require_relative "../logger/logging"
3
6
 
4
7
  module Kanal
5
8
  module Core
@@ -8,6 +11,8 @@ module Kanal
8
11
  # It is served as some kind of namespace for conditions, with specific
9
12
  # name of pack and helper methods
10
13
  class ConditionPack
14
+ include Logging
15
+
11
16
  attr_reader :name
12
17
 
13
18
  def initialize(name)
@@ -18,7 +23,11 @@ module Kanal
18
23
  def get_condition_by_name!(name)
19
24
  condition = get_condition_by_name name
20
25
 
21
- raise "Condition #{name} was not found in pack #{@name}. Maybe it was not added?" unless condition
26
+ unless condition
27
+ logger.fatal "Attempted to get condition #{name} in pack #{@name}"
28
+
29
+ raise "Condition #{name} was not found in pack #{@name}. Maybe it was not added?"
30
+ end
22
31
 
23
32
  condition
24
33
  end
@@ -28,9 +37,20 @@ module Kanal
28
37
  end
29
38
 
30
39
  def register_condition(condition)
31
- raise "Can register only conditions that inherit Condition class" unless condition.is_a? Condition
40
+ logger.info "Attempting to register condition '#{condition.name}'"
41
+
42
+ unless condition.is_a? Condition
43
+ logger.fatal "Attempted to register condition which isn't of Condition class"
44
+
45
+ raise "Can register only conditions that inherit Condition class"
46
+ end
47
+
48
+ if condition_registered? condition
49
+ logger.warn "Condition '#{condition.name}' already registered"
50
+ return self
51
+ end
32
52
 
33
- return self if condition_registered? condition
53
+ logger.info "Registering condition '#{condition.name}'"
34
54
 
35
55
  @conditions.append condition
36
56
 
@@ -1,5 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "./condition_pack"
2
4
  require_relative "./condition_creator"
5
+ require_relative "../logger/logging"
3
6
 
4
7
  module Kanal
5
8
  module Core
@@ -7,6 +10,8 @@ module Kanal
7
10
  # This class helps in condition pack creation
8
11
  # with the help of dsl
9
12
  class ConditionPackCreator
13
+ include Logging
14
+
10
15
  TEMP_NAME = :temp_name
11
16
 
12
17
  def initialize(name)
@@ -17,9 +22,19 @@ module Kanal
17
22
  def create(&block)
18
23
  instance_eval(&block)
19
24
 
20
- raise "Please provide condition pack name" unless @name
25
+ unless @name
26
+ logger.fatal "Attempted to create condition pack without name"
27
+
28
+ raise "Please provide condition pack name"
29
+ end
30
+
31
+ if @conditions.empty?
32
+ logger.fatal "Attempted to create condition pack #{@name} without conditions provided"
33
+
34
+ raise "Please provide conditions for condition pack #{@name}"
35
+ end
21
36
 
22
- raise "Please provide conditions for condition pack #{@name}" if @conditions.empty?
37
+ logger.info "Creating condition pack '#{@name}'"
23
38
 
24
39
  pack = ConditionPack.new(@name)
25
40
 
@@ -1,9 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../logger/logging"
4
+
1
5
  module Kanal
2
6
  module Core
3
7
  module Conditions
4
8
  # This class contains all needed functionality to store,
5
9
  # search conditions
6
10
  class ConditionStorage
11
+ include Logging
12
+
7
13
  def initialize
8
14
  @condition_packs = []
9
15
  end
@@ -11,7 +17,11 @@ module Kanal
11
17
  def get_condition_pack_by_name!(name)
12
18
  pack = get_condition_pack_by_name name
13
19
 
14
- raise "Condition pack #{name} is not registered, but was requested from ConditionStorage" unless pack
20
+ unless pack
21
+ logger.fatal "Attempted to request unregistered condition pack #{name} from ConditionStorage"
22
+
23
+ raise "Condition pack #{name} is not registered, but was requested from ConditionStorage"
24
+ end
15
25
 
16
26
  pack
17
27
  end
@@ -44,7 +54,13 @@ module Kanal
44
54
  def register_condition_pack(pack)
45
55
  return if condition_pack_exists? pack
46
56
 
47
- raise "Condition pack should be descendant of ConditionPack class" unless pack.is_a? ConditionPack
57
+ unless pack.is_a? ConditionPack
58
+ logger.fatal "Attempted to register condition pack which isn't of ConditionPack class"
59
+
60
+ raise "Condition pack should be descendant of ConditionPack class"
61
+ end
62
+
63
+ logger.info "Registering condition pack '#{pack.name}'"
48
64
 
49
65
  @condition_packs.append pack
50
66
  end
@@ -8,6 +8,8 @@ require_relative "./helpers/parameter_registrator"
8
8
  require_relative "./plugins/plugin"
9
9
  require_relative "./input/input"
10
10
  require_relative "./services/service_container"
11
+ require_relative "./logger/logging"
12
+
11
13
 
12
14
  module Kanal
13
15
  module Core
@@ -34,6 +36,7 @@ module Kanal
34
36
  include Plugins
35
37
  include Hooks
36
38
  include Services
39
+ include Logging
37
40
 
38
41
  # @return [Kanal::Core::Conditions::ConditionStorage]
39
42
  attr_reader :condition_storage
@@ -74,6 +77,8 @@ module Kanal
74
77
  #
75
78
  def register_plugin(plugin)
76
79
  unless plugin.is_a? Plugin
80
+ logger.fatal "Attempted to register plugin that is not of type Kanal::Core::Plugin or a class that inherits base Plugin class"
81
+
77
82
  raise "Plugin must be of type Kanal::Core::Plugin or be a class that inherits base Plugin class"
78
83
  end
79
84
 
@@ -82,10 +87,16 @@ module Kanal
82
87
  name = plugin.name
83
88
 
84
89
  # TODO: _log that plugin already registered with such name
85
- return if !name.nil? && plugin_registered?(name)
90
+ if !name.nil? && plugin_registered?(name)
91
+ logger.warn "Plugin '#{name}' already registered"
92
+
93
+ return
94
+ end
86
95
 
87
96
  plugin.setup(self)
88
97
 
98
+ logger.info "Registering plugin '#{name}'"
99
+
89
100
  @plugins.append plugin
90
101
  # NOTE: Catching here Exception because metho.name can raise ScriptError (derived from Exception)
91
102
  # and method .setup can raise ANY type of error.
@@ -101,6 +112,8 @@ module Kanal
101
112
  end
102
113
 
103
114
  # TODO: _log this info in critical error instead of raising exception
115
+ logger.fatal "There was a problem while registering plugin named: #{name}. Error: `#{e}`."
116
+
104
117
  raise "There was a problem while registering plugin named: #{name}. Error: `#{e}`.
105
118
  Remember, plugin errors are often due to .name method not overriden or
106
119
  having faulty code inside .setup overriden method"
@@ -153,6 +166,8 @@ module Kanal
153
166
  # @return [void] <description>
154
167
  #
155
168
  def register_input_parameter(name, readonly: false)
169
+ logger.info "Registering input parameter: '#{name}', readonly: '#{readonly}'"
170
+
156
171
  @input_parameter_registrator.register_parameter name, readonly: readonly
157
172
  end
158
173
 
@@ -165,6 +180,7 @@ module Kanal
165
180
  # @return [void] <description>
166
181
  #
167
182
  def register_output_parameter(name, readonly: false)
183
+ logger.info "Registering output parameter: '#{name}', readonly: '#{readonly}'"
168
184
  @output_parameter_registrator.register_parameter name, readonly: readonly
169
185
  end
170
186
 
@@ -179,6 +195,8 @@ module Kanal
179
195
  # @return [void] <description>
180
196
  #
181
197
  def add_condition_pack(name, &block)
198
+ logger.info "Starting to create condition pack '#{name}'"
199
+
182
200
  creator = ConditionPackCreator.new name
183
201
 
184
202
  pack = creator.create(&block)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Kanal
2
4
  module Core
3
5
  module Helpers
@@ -17,8 +19,7 @@ module Kanal
17
19
  end
18
20
 
19
21
  class ConditionFinder
20
- def find_by_name(name)
21
- end
22
+ def find_by_name(name); end
22
23
  end
23
24
  end
24
25
  end
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "parameter_bag"
4
+ require_relative "../logger/logging"
2
5
 
3
6
  module Kanal
4
7
  module Core
@@ -7,6 +10,8 @@ module Kanal
7
10
  # and if they are has needed by registrator allowances, types etc, whatever
8
11
  # registrator rules are stored for property
9
12
  class ParameterBagWithRegistrator < ParameterBag
13
+ include Logging
14
+
10
15
  def initialize(registrator)
11
16
  super()
12
17
  @registrator = registrator
@@ -27,6 +32,9 @@ module Kanal
27
32
  value_exists = !get(name).nil?
28
33
 
29
34
  if value_exists
35
+ logger.fatal "Parameter #{name} is marked readonly! Attempted to set it's value, but
36
+ it already has value."
37
+
30
38
  raise "Parameter #{name} is marked readonly! You tried to set it's value, but
31
39
  it already has value."
32
40
  end
@@ -37,6 +45,8 @@ module Kanal
37
45
 
38
46
  def validate_parameter_registration(name)
39
47
  unless @registrator.parameter_registered? name
48
+ logger.fatal "Parameter #{name} was not registered! Did you forget to register that parameter?"
49
+
40
50
  raise "Parameter #{name} was not registered! Did you forget to register that parameter?"
41
51
  end
42
52
  end
@@ -18,18 +18,16 @@ module Kanal
18
18
  # input.prop = 123
19
19
  if symbol.to_s.include? "="
20
20
  @parameter_bag.set parameter_name, args.first
21
- else
21
+ elsif !args.empty?
22
22
  # this approach can be used also in dsl
23
23
  # like that
24
24
  # setters: prop value
25
25
  # getters: prop
26
- if !args.empty?
27
- # means it is used as setter in dsl,
28
- # method call with argument
29
- @parameter_bag.set(parameter_name, *args)
30
- else
31
- @parameter_bag.get parameter_name
32
- end
26
+ @parameter_bag.set(parameter_name, *args)
27
+ # means it is used as setter in dsl,
28
+ # method call with argument
29
+ else
30
+ @parameter_bag.get parameter_name
33
31
  end
34
32
  end
35
33
  end
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../logger/logging"
4
+
1
5
  module Kanal
2
6
  module Core
3
7
  module Helpers
@@ -17,6 +21,8 @@ module Kanal
17
21
  # Class holds parameter names that are allowed
18
22
  # to be used.
19
23
  class ParameterRegistrator
24
+ include Logging
25
+
20
26
  def initialize
21
27
  @parameters_by_name = {}
22
28
  end
@@ -25,7 +31,13 @@ module Kanal
25
31
  # be changed. handy for input parameters populated by interface or
26
32
  # whatever
27
33
  def register_parameter(name, readonly: false)
28
- raise "Parameter named #{name} already registered!" if @parameters_by_name.key? name
34
+ if @parameters_by_name.key? name
35
+ logger.fatal "Attempted to register already registered parameter '#{name}'"
36
+
37
+ raise "Parameter named #{name} already registered!"
38
+ end
39
+
40
+ logger.info "Registering parameter '#{name}'"
29
41
 
30
42
  registration = ParameterRegistration.new readonly
31
43
 
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../hooks/hook_storage"
4
+
5
+ module Kanal
6
+ module Core
7
+ module Helpers
8
+ class Queue
9
+ include Hooks
10
+
11
+ attr_reader :hooks
12
+
13
+ def initialize
14
+ @items = []
15
+ @hooks = HookStorage.new
16
+ hooks.register(:item_queued) # args arguments: item
17
+ end
18
+
19
+ def enqueue(element)
20
+ @items.append element
21
+ @hooks.call :item_queued, element
22
+ end
23
+
24
+ def dequeue
25
+ @items.shift
26
+ end
27
+
28
+ def empty?
29
+ @items.empty?
30
+ end
31
+
32
+ def remove(element)
33
+ @items.delete(element)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kanal
4
+ module Core
5
+ module Helpers
6
+ class ResponseBlock
7
+ attr_reader :block
8
+
9
+ def initialize(block, async: false)
10
+ @block = block
11
+ @async = async
12
+ end
13
+
14
+ def async?
15
+ @async
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../output/output"
4
+ require_relative "../logger/logging"
5
+
6
+ module Kanal
7
+ module Core
8
+ module Helpers
9
+ class ResponseExecutionBlock
10
+ include Output
11
+ include Logging
12
+
13
+ attr_reader :response_block, :input
14
+
15
+ def initialize(response_block, input, default_error_node, error_node)
16
+ @response_block = response_block
17
+ @input = input
18
+ @default_error_node = default_error_node
19
+ @error_node = error_node
20
+ end
21
+
22
+ def execute(core, output_queue)
23
+ if response_block.async?
24
+ # NOTE: Thread doesnt just die here - it's execution is continued in output_queue.enqueue in router
25
+ # then :item_queued hook is called inside and subsequently output_ready_block gets called in this thread
26
+ # TODO: be aware that this can cause unexpected behaviour. Maybe think how to rework it.
27
+ Thread.new do
28
+ output_queue.enqueue construct_output(core)
29
+ end
30
+ else
31
+ output_queue.enqueue construct_output(core)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def construct_output(core)
38
+ logger.info "Constructing output for input ##{input.__id__}"
39
+
40
+ output = Output::Output.new core.output_parameter_registrator, input, core
41
+
42
+ begin
43
+ output.instance_eval(&@response_block.block)
44
+
45
+ core.hooks.call :output_before_returned, input, output
46
+ rescue => e
47
+ logger.error "Failed to construct output for input ##{input.__id__}. Error: '#{e}'"
48
+
49
+ output = Output::Output.new core.output_parameter_registrator, input, core
50
+
51
+ error_node = @error_node || @default_error_node
52
+
53
+ logger.info "Trying to construct error response for input ##{input.__id__}. Error response is default: #{@error_node.nil?}"
54
+
55
+ begin
56
+ output.instance_eval(&error_node.response_blocks.first.block)
57
+
58
+ core.hooks.call :output_before_returned, input, output
59
+ rescue => e
60
+ logger.error "Failed to construct error response for input ##{input.__id__}. Error: '#{e}'"
61
+
62
+ logger.info "Trying to construct default error response for input ##{input.__id__}"
63
+
64
+ output.instance_eval(&@default_error_node.response_blocks.first.block)
65
+
66
+ core.hooks.call :output_before_returned, input, output
67
+ end
68
+ end
69
+
70
+ logger.info "Output ##{output.__id__} for input ##{input.__id__} constructed"
71
+
72
+ output
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "method_source"
2
4
 
3
5
  module Kanal
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../logger/logging"
4
+
3
5
  module Kanal
4
6
  module Core
5
7
  module Hooks
@@ -8,6 +10,8 @@ module Kanal
8
10
  # attaching to hooks, calling hooks with arguments
9
11
  #
10
12
  class HookStorage
13
+ include Logging
14
+
11
15
  def initialize
12
16
  @listeners = {}
13
17
  end
@@ -29,7 +33,12 @@ module Kanal
29
33
  # Wondrous world of dynamic languages 🌈🦄
30
34
  #
31
35
  def register(name)
32
- return if hook_exists? name
36
+ if hook_exists? name
37
+ logger.warn "Hook '#{name}' already exists"
38
+ return
39
+ end
40
+
41
+ logger.info "Registering hook '#{name}'"
33
42
 
34
43
  @listeners[name] = []
35
44
  end
@@ -81,9 +90,7 @@ module Kanal
81
90
  # @return [void] <description>
82
91
  #
83
92
  def attach(name, &block)
84
- unless hook_exists? name
85
- raise "You cannot listen to hook that does not exist! Hook in question: #{name}"
86
- end
93
+ raise "You cannot listen to hook that does not exist! Hook in question: #{name}" unless hook_exists? name
87
94
 
88
95
  proc_to_lambda_object = Object.new
89
96
  proc_to_lambda_object.define_singleton_method(:hook_block, &block)
@@ -24,6 +24,11 @@ module Kanal
24
24
  #
25
25
  def initialize(core)
26
26
  @core = core
27
+
28
+ _this = self
29
+ @core.router.output_ready do |output|
30
+ _this.consume_output output
31
+ end
27
32
  end
28
33
 
29
34
  #
@@ -59,6 +64,14 @@ module Kanal
59
64
  def stop
60
65
  raise NotImplementedError
61
66
  end
67
+
68
+ def consume_input(input)
69
+ @core.router.consume_input input
70
+ end
71
+
72
+ def consume_output(output)
73
+ raise NotImplementedError
74
+ end
62
75
  end
63
76
  end
64
77
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+
5
+ module Kanal
6
+ module Core
7
+ module Logging
8
+ # Logger instance will be saved inside class itself for future calls
9
+ @logger_instance
10
+
11
+ # Mixing in Logger in some class adds possibility to use logger instance method
12
+ def logger
13
+ @logger_instance ||= Logging.create_logger self.class.name
14
+ end
15
+
16
+ class << self
17
+ def create_logger(class_name)
18
+ logger = Logger.new STDOUT
19
+ logger.progname = class_name.rpartition(':').last
20
+ logger.datetime_format = "%d-%m-%Y %H:%M:%S"
21
+ logger
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Kanal
2
4
  module Core
3
5
  module Output
@@ -8,9 +10,7 @@ module Kanal
8
10
  @input = input
9
11
  end
10
12
 
11
- def create(&block)
12
-
13
- end
13
+ def create(&block); end
14
14
  end
15
15
  end
16
16
  end
@@ -31,7 +31,7 @@ module Kanal
31
31
 
32
32
  #
33
33
  # If plugins does have rake tasks available for execution,
34
- # require them here. They will be used
34
+ # require them here. They will be used
35
35
  #
36
36
  # @return [Array<Rake::TaskLib>] <description>
37
37
  #
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "./router_node"
4
+ require_relative "../helpers/queue"
5
+ require_relative "../helpers/response_execution_block"
6
+ require_relative "../logger/logging"
4
7
 
5
8
  module Kanal
6
9
  module Core
@@ -10,17 +13,50 @@ module Kanal
10
13
  # namespace. Basically class router stores all the
11
14
  # router nodes and have a name.
12
15
  class Router
13
- attr_reader :name,
14
- :core
16
+ include Helpers
17
+ include Logging
18
+
19
+ attr_reader :name, :core, :output_ready_block
15
20
 
16
21
  def initialize(name, core)
22
+ logger.info "Initializing"
23
+
17
24
  @name = name
18
25
  @core = core
19
26
  @root_node = nil
20
27
  @default_node = nil
28
+ @default_error_node = nil
29
+ default_error_response do
30
+ if core.plugin_registered? :batteries
31
+ body "Unfortunately, error happened. Please consider contacting the creator of this bot to provide information about the circumstances of this error."
32
+ else
33
+ raise "Error occurred and there is no way to inform end user about it. You can override error response with router.error_response method or register :batteries plugin so default response will populate the .body output parameter"
34
+ end
35
+ end
36
+ @error_node = nil
37
+ @response_execution_queue = Queue.new
38
+ @output_queue = Queue.new
39
+ @output_ready_block = nil
40
+ @core.hooks.register(:output_ready) # arg
41
+
42
+ _this = self
43
+ _output_queue = @output_queue
44
+ @output_queue.hooks.attach :item_queued do |output|
45
+ _this.logger.info "Calling output_ready block for input ##{output.input.__id__} and output #{output.__id__}. Output body is: '#{output.body}'"
46
+
47
+ begin
48
+ _this.output_ready_block.call output
49
+ _output_queue.remove(output)
50
+ rescue
51
+ _output_queue.remove(output)
52
+ raise "Error in output_ready block!"
53
+ end
54
+ end
21
55
  end
22
56
 
23
57
  def configure(&block)
58
+ logger.info "Configuring"
59
+
24
60
  # Root node does not have parent
25
61
  @root_node ||= RouterNode.new router: self, parent: nil, root: true
26
62
 
@@ -28,24 +64,56 @@ module Kanal
28
64
  end
29
65
 
30
66
  def default_response(&block)
31
- raise "default node for router #{@name} already defined" if @default_node
67
+ logger.info "Setting default response"
68
+
69
+ if @default_node
70
+ logger.fatal "Attempted to set default_response for a second time"
71
+
72
+ raise "default node for router #{@name} already defined"
73
+ end
32
74
 
33
75
  @default_node = RouterNode.new parent: nil, router: self, default: true
34
76
 
35
77
  @default_node.respond(&block)
36
78
  end
37
79
 
38
- # Main method for creating output if it is found or going to default output
39
- def create_output_for_input(input)
80
+ def error_response(&block)
81
+ raise "error node for router #{@name} already defined" if @error_node
82
+
83
+ @error_node = RouterNode.new parent: nil, router: self, error: true
84
+
85
+ @error_node.respond(&block)
86
+ end
87
+
88
+ # Main method for creating output(s) if it is found or going to default output
89
+ def consume_input(input)
90
+ logger.info "Consuming input #{input.__id__}."
91
+
40
92
  # Checking if default node with output exists throw error if not
41
- raise "Please provide default response for router before you try and throw input against it ;)" unless @default_node
93
+ unless @default_node
94
+ logger.fatal "Attempted to consume input with no default response set"
95
+
96
+ raise "Please provide default response for router before you try and throw input against it ;)"
97
+ end
98
+
99
+ unless @root_node
100
+ logger.fatal "Attempted to consume input but router is not configured"
42
101
 
43
- raise "You did not actually .configure router, didn't you? There is no even root node! Use .configure method" unless @root_node
102
+ raise "You did not actually .configure router, didn't you? There is no even root node! Use .configure method"
103
+ end
44
104
 
45
105
  unless @root_node.children?
106
+ logger.fatal "Attempted to consume input but router does not have any routes"
107
+
46
108
  raise "Hey your router actually does not have ANY routes to work with. Did you even try adding them?"
47
109
  end
48
110
 
111
+ unless @output_ready_block
112
+ logger.fatal "Attempted to consume input but output_ready block is not set"
113
+
114
+ raise "You must provide block via .output_ready for router to function properly"
115
+ end
116
+
49
117
  @core.hooks.call :input_before_router, input
50
118
 
51
119
  node = test_input_against_router_node input, @root_node
@@ -54,11 +122,31 @@ module Kanal
54
122
  # using default response
55
123
  node ||= @default_node
56
124
 
57
- output = node.construct_response input
125
+ response_blocks = node.response_blocks
126
+
127
+ error_node = @error_node || @default_error_node
128
+
129
+ response_execution_blocks = response_blocks.map { |rb| ResponseExecutionBlock.new rb, input, @default_error_node, @error_node }
130
+
131
+ response_execution_blocks.each do |reb|
132
+ @response_execution_queue.enqueue reb
133
+ end
134
+
135
+ process_response_execution_queue
136
+ end
58
137
 
59
- @core.hooks.call :output_before_returned, input, output
138
+ def process_response_execution_queue
139
+ until @response_execution_queue.empty?
140
+ response_execution = @response_execution_queue.dequeue
60
141
 
61
- output
142
+ response_execution.execute core, @output_queue
143
+ end
144
+ end
145
+
146
+ def output_ready(&block)
147
+ logger.info "Setting output_ready block"
148
+
149
+ @output_ready_block = block
62
150
  end
63
151
 
64
152
  # Recursive method for searching router nodes
@@ -91,6 +179,12 @@ module Kanal
91
179
  end
92
180
 
93
181
  private :test_input_against_router_node
182
+
183
+ def default_error_response(&block)
184
+ @default_error_node = RouterNode.new parent: nil, router: self, error: true
185
+
186
+ @default_error_node.respond(&block)
187
+ end
94
188
  end
95
189
  end
96
190
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../output/output"
4
+ require_relative "../helpers/response_block"
5
+ require_relative "../logger/logging"
4
6
 
5
7
  module Kanal
6
8
  module Core
@@ -11,6 +13,8 @@ module Kanal
11
13
  # tree, containing conditions and responses
12
14
  class RouterNode
13
15
  include Output
16
+ include Helpers
17
+ include Logging
14
18
 
15
19
  attr_reader :parent,
16
20
  :children
@@ -18,13 +22,13 @@ module Kanal
18
22
  # parameter default: is for knowing that this node
19
23
  # is for default response
20
24
  # default response cannot have child nodes
21
- def initialize(*args, router:, parent:, default: false, root: false)
25
+ def initialize(*args, router:, parent:, default: false, root: false, error: false)
22
26
  @router = router
23
27
  @parent = parent
24
28
 
25
29
  @children = []
26
30
 
27
- @response_block = nil
31
+ @response_blocks = []
28
32
 
29
33
  @condition_pack_name = nil
30
34
  @condition_name = nil
@@ -32,7 +36,7 @@ module Kanal
32
36
 
33
37
  # We omit setting conditions because default router node does not need any conditions
34
38
  # Also root node does not have conditions so we basically omit them if arguments are empty
35
- return if default || root
39
+ return if default || root || error
36
40
 
37
41
  # With this we attach names of condition pack and condition to this router
38
42
  # node, so we will be able to find them later at runtime and use them
@@ -48,24 +52,28 @@ module Kanal
48
52
  child.instance_eval(&block)
49
53
  end
50
54
 
51
- def construct_response(input)
52
- raise "no response block configured for this node. router: #{@router.name}. debug: #{debug_info}" unless @response_block
55
+ def response_blocks
56
+ if @response_blocks.empty?
57
+ raise "no response block configured for this node. router: #{@router.name}. debug: #{debug_info}"
58
+ end
53
59
 
54
- output = Output::Output.new @router.core.output_parameter_registrator, input, @router.core
60
+ @response_blocks
61
+ end
55
62
 
56
- output.instance_eval(&@response_block)
63
+ def respond(&block)
64
+ raise "Router node with children cannot have response" unless @children.empty?
57
65
 
58
- output
66
+ @response_blocks.append ResponseBlock.new(block)
59
67
  end
60
68
 
61
- def respond(&block)
69
+ def respond_async(&block)
62
70
  raise "Router node with children cannot have response" unless @children.empty?
63
71
 
64
- @response_block = block
72
+ @response_blocks.append ResponseBlock.new(block, async: true)
65
73
  end
66
74
 
67
75
  def response?
68
- !@response_block.nil?
76
+ !@response_blocks.empty?
69
77
  end
70
78
 
71
79
  # This method processes args to populate condition and condition pack
@@ -88,6 +96,8 @@ module Kanal
88
96
  condition = pack.get_condition_by_name! condition_name
89
97
 
90
98
  if condition.with_argument? && !@condition_argument
99
+ logger.fatal "Condition requires argument, but was provided as :symbol, not as positional_arg:"
100
+
91
101
  raise "Condition requires argument, though you wrote it as :symbol, not as positional_arg:
92
102
  Please check route with condition pack: #{condition_pack_name} and condition: #{condition_name}"
93
103
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "./router"
2
4
 
3
5
  module Kanal
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../logger/logging"
4
+
1
5
  module Kanal
2
6
  module Core
3
7
  module Services
@@ -24,6 +28,8 @@ module Kanal
24
28
  # lifespan types.
25
29
  #
26
30
  class ServiceContainer
31
+ include Logging
32
+
27
33
  TYPE_SINGLETON = :singleton
28
34
  TYPE_TRANSIENT = :transient
29
35
 
@@ -45,12 +51,24 @@ module Kanal
45
51
  # @return [void] <description>
46
52
  #
47
53
  def register_service(name, service_class, type: TYPE_SINGLETON, &block)
48
- return if @registrations.key? name
54
+ logger.info "Trying to register service '#{name}'"
49
55
 
50
- raise "Unrecognized service type #{type}. Allowed types: #{allowed_types}" unless allowed_types.include? type
56
+ if @registrations.key? name
57
+ logger.warn "Attempted to register service '#{name}', but it is already registered"
58
+
59
+ return
60
+ end
61
+
62
+ unless allowed_types.include? type
63
+ logger.fatal "Attempted to register service type '#{type}'."
64
+
65
+ raise "Unrecognized service type #{type}. Allowed types: #{allowed_types}"
66
+ end
51
67
 
52
68
  registration = ServiceRegistration.new service_class, type, block
53
69
 
70
+ logger.info "Registering service '#{name}'"
71
+
54
72
  @registrations[name] = registration
55
73
  end
56
74
 
@@ -66,14 +84,13 @@ module Kanal
66
84
 
67
85
  registration = @registrations[name]
68
86
 
69
- if registration.type == TYPE_SINGLETON
87
+ case registration.type
88
+ when TYPE_SINGLETON
70
89
  # Created once and reused after creation
71
- if @services[name].nil?
72
- @services[name] = create_service_from_registration registration
73
- end
90
+ @services[name] = create_service_from_registration registration if @services[name].nil?
74
91
 
75
92
  @services[name]
76
- elsif registration.type == TYPE_TRANSIENT
93
+ when TYPE_TRANSIENT
77
94
  # Created every time
78
95
  create_service_from_registration registration
79
96
  end
data/lib/kanal/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kanal
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kanal
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - idchlife
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-01-29 00:00:00.000000000 Z
11
+ date: 2023-03-10 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Thanks to the core library, ecosystem of Kanal tools can be extendted
14
14
  to use with input-output bot-like behaviour, with routing
@@ -42,11 +42,14 @@ files:
42
42
  - lib/kanal/core/helpers/parameter_bag_with_registrator.rb
43
43
  - lib/kanal/core/helpers/parameter_finder_with_method_missing_mixin.rb
44
44
  - lib/kanal/core/helpers/parameter_registrator.rb
45
+ - lib/kanal/core/helpers/queue.rb
46
+ - lib/kanal/core/helpers/response_block.rb
47
+ - lib/kanal/core/helpers/response_execution_block.rb
45
48
  - lib/kanal/core/helpers/router_proc_parser.rb
46
49
  - lib/kanal/core/hooks/hook_storage.rb
47
50
  - lib/kanal/core/input/input.rb
48
51
  - lib/kanal/core/interfaces/interface.rb
49
- - lib/kanal/core/logger/logger.rb
52
+ - lib/kanal/core/logger/logging.rb
50
53
  - lib/kanal/core/output/output.rb
51
54
  - lib/kanal/core/output/output_creator.rb
52
55
  - lib/kanal/core/plugins/plugin.rb
@@ -68,6 +71,7 @@ metadata:
68
71
  homepage_uri: https://idchlife.github.io/kanal-documentation/
69
72
  source_code_uri: https://github.com/idchlife/kanal
70
73
  changelog_uri: https://github.com/idchlife/kanal/CHANGELOG.md
74
+ rubygems_mfa_required: 'true'
71
75
  post_install_message:
72
76
  rdoc_options: []
73
77
  require_paths:
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "logger"
4
-
5
- module Kanal
6
- module Core
7
- module Logger
8
- class Logger < ::Logger
9
- end
10
- end
11
- end
12
- end