rita 0.1.0 → 5.0.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +19 -0
  3. data/.rubocop.yml +51 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +16 -0
  6. data/CONTRIBUTING.md +18 -0
  7. data/Gemfile +5 -0
  8. data/{LICENSE.txt → LICENSE} +1 -3
  9. data/README.md +17 -24
  10. data/Rakefile +5 -5
  11. data/bin/lita +7 -0
  12. data/lib/lita/adapter.rb +147 -0
  13. data/lib/lita/adapters/shell.rb +126 -0
  14. data/lib/lita/adapters/test.rb +62 -0
  15. data/lib/lita/authorization.rb +112 -0
  16. data/lib/lita/callback.rb +39 -0
  17. data/lib/lita/cli.rb +218 -0
  18. data/lib/lita/configurable.rb +47 -0
  19. data/lib/lita/configuration_builder.rb +247 -0
  20. data/lib/lita/configuration_validator.rb +95 -0
  21. data/lib/lita/default_configuration.rb +122 -0
  22. data/lib/lita/errors.rb +25 -0
  23. data/lib/lita/handler/chat_router.rb +141 -0
  24. data/lib/lita/handler/common.rb +208 -0
  25. data/lib/lita/handler/event_router.rb +84 -0
  26. data/lib/lita/handler/http_router.rb +31 -0
  27. data/lib/lita/handler.rb +15 -0
  28. data/lib/lita/handlers/authorization.rb +129 -0
  29. data/lib/lita/handlers/help.rb +171 -0
  30. data/lib/lita/handlers/info.rb +66 -0
  31. data/lib/lita/handlers/room.rb +36 -0
  32. data/lib/lita/handlers/users.rb +37 -0
  33. data/lib/lita/http_callback.rb +46 -0
  34. data/lib/lita/http_route.rb +83 -0
  35. data/lib/lita/logger.rb +43 -0
  36. data/lib/lita/message.rb +124 -0
  37. data/lib/lita/middleware_registry.rb +39 -0
  38. data/lib/lita/namespace.rb +29 -0
  39. data/lib/lita/plugin_builder.rb +43 -0
  40. data/lib/lita/rack_app.rb +100 -0
  41. data/lib/lita/registry.rb +164 -0
  42. data/lib/lita/response.rb +65 -0
  43. data/lib/lita/robot.rb +273 -0
  44. data/lib/lita/room.rb +119 -0
  45. data/lib/lita/route_validator.rb +82 -0
  46. data/lib/lita/rspec/handler.rb +127 -0
  47. data/lib/lita/rspec/matchers/chat_route_matcher.rb +53 -0
  48. data/lib/lita/rspec/matchers/event_route_matcher.rb +29 -0
  49. data/lib/lita/rspec/matchers/http_route_matcher.rb +34 -0
  50. data/lib/lita/rspec.rb +48 -0
  51. data/lib/lita/source.rb +81 -0
  52. data/lib/lita/store.rb +23 -0
  53. data/lib/lita/target.rb +3 -0
  54. data/lib/lita/template.rb +71 -0
  55. data/lib/lita/template_resolver.rb +52 -0
  56. data/lib/lita/timer.rb +49 -0
  57. data/lib/lita/user.rb +157 -0
  58. data/lib/lita/util.rb +31 -0
  59. data/lib/lita/version.rb +6 -0
  60. data/lib/lita.rb +166 -0
  61. data/rita.gemspec +50 -0
  62. data/spec/lita/adapter_spec.rb +54 -0
  63. data/spec/lita/adapters/shell_spec.rb +99 -0
  64. data/spec/lita/authorization_spec.rb +122 -0
  65. data/spec/lita/configuration_builder_spec.rb +247 -0
  66. data/spec/lita/configuration_validator_spec.rb +114 -0
  67. data/spec/lita/default_configuration_spec.rb +242 -0
  68. data/spec/lita/handler/chat_router_spec.rb +236 -0
  69. data/spec/lita/handler/common_spec.rb +289 -0
  70. data/spec/lita/handler/event_router_spec.rb +121 -0
  71. data/spec/lita/handler/http_router_spec.rb +155 -0
  72. data/spec/lita/handler_spec.rb +62 -0
  73. data/spec/lita/handlers/authorization_spec.rb +111 -0
  74. data/spec/lita/handlers/help_spec.rb +124 -0
  75. data/spec/lita/handlers/info_spec.rb +67 -0
  76. data/spec/lita/handlers/room_spec.rb +24 -0
  77. data/spec/lita/handlers/users_spec.rb +35 -0
  78. data/spec/lita/logger_spec.rb +28 -0
  79. data/spec/lita/message_spec.rb +178 -0
  80. data/spec/lita/plugin_builder_spec.rb +41 -0
  81. data/spec/lita/response_spec.rb +62 -0
  82. data/spec/lita/robot_spec.rb +285 -0
  83. data/spec/lita/room_spec.rb +136 -0
  84. data/spec/lita/rspec/handler_spec.rb +33 -0
  85. data/spec/lita/rspec_spec.rb +162 -0
  86. data/spec/lita/source_spec.rb +68 -0
  87. data/spec/lita/store_spec.rb +23 -0
  88. data/spec/lita/template_resolver_spec.rb +42 -0
  89. data/spec/lita/template_spec.rb +52 -0
  90. data/spec/lita/timer_spec.rb +32 -0
  91. data/spec/lita/user_spec.rb +167 -0
  92. data/spec/lita/util_spec.rb +18 -0
  93. data/spec/lita_spec.rb +227 -0
  94. data/spec/spec_helper.rb +35 -0
  95. data/spec/templates/basic.erb +1 -0
  96. data/spec/templates/basic.irc.erb +1 -0
  97. data/spec/templates/helpers.erb +1 -0
  98. data/spec/templates/interpolated.erb +1 -0
  99. data/templates/locales/en.yml +137 -0
  100. data/templates/plugin/Gemfile +5 -0
  101. data/templates/plugin/README.tt +29 -0
  102. data/templates/plugin/Rakefile +8 -0
  103. data/templates/plugin/gemspec.tt +27 -0
  104. data/templates/plugin/gitignore +18 -0
  105. data/templates/plugin/lib/lita/plugin_type/plugin.tt +19 -0
  106. data/templates/plugin/lib/plugin.tt +16 -0
  107. data/templates/plugin/locales/en.yml.tt +4 -0
  108. data/templates/plugin/spec/lita/plugin_type/plugin_spec.tt +6 -0
  109. data/templates/plugin/spec/spec_helper.tt +8 -0
  110. data/templates/plugin/templates/gitkeep +0 -0
  111. data/templates/robot/Gemfile +5 -0
  112. data/templates/robot/lita_config.rb +28 -0
  113. metadata +386 -21
  114. data/.standard.yml +0 -3
  115. data/CHANGELOG.md +0 -5
  116. data/CODE_OF_CONDUCT.md +0 -132
  117. data/lib/rita/version.rb +0 -5
  118. data/lib/rita.rb +0 -8
  119. data/sig/rita.rbs +0 -4
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lita
4
+ # Represents the action that is taken when a route or event is triggered. It
5
+ # can be a block or the name of a method on object.
6
+ # @since 4.0.0
7
+ # @api private
8
+ class Callback
9
+ # A block that should be used as the callback.
10
+ attr_reader :block
11
+
12
+ # The name of the method in the plugin that should be called as the callback.
13
+ attr_reader :method_name
14
+
15
+ # @overload initialize(method_name)
16
+ # @param method_name [String, Symbol] The name of the instance method that serves as the
17
+ # callback.
18
+ # @overload initialize(callable)
19
+ # @param callable [Proc] A callable object to use as the callback.
20
+ def initialize(method_name_or_callable)
21
+ if method_name_or_callable.respond_to?(:call)
22
+ @block = method_name_or_callable
23
+ else
24
+ @method_name = method_name_or_callable
25
+ end
26
+ end
27
+
28
+ # Invokes the callback.
29
+ def call(host, *args)
30
+ if block
31
+ host.instance_exec(*args, &block)
32
+ else
33
+ host.public_send(method_name, *args)
34
+ end
35
+
36
+ true
37
+ end
38
+ end
39
+ end
data/lib/lita/cli.rb ADDED
@@ -0,0 +1,218 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler"
4
+ require "i18n"
5
+ require "thor"
6
+
7
+ require_relative "../lita"
8
+ require_relative "version"
9
+
10
+ module Lita
11
+ # The command line interface for Lita.
12
+ class CLI < Thor
13
+ include Thor::Actions
14
+
15
+ # The root file path for the templates directory.
16
+ # @note This is a magic method required by Thor for file operations.
17
+ # @return [String] The path.
18
+ def self.source_root
19
+ Lita.template_root
20
+ end
21
+
22
+ # Returns the full destination file path for the given file, using the supplied +default_path+
23
+ # as the base if run as root, otherwise falling back to the user's home directory.
24
+ # @param file_name [String] The name of the file.
25
+ # @param default_path [String] The base of the file path to use when run as root.
26
+ # @return [String] The full file path.
27
+ def self.file_path_for(file_name, default_path)
28
+ base_path = Process.euid.zero? ? default_path : ENV["HOME"]
29
+ File.join(base_path, file_name)
30
+ end
31
+
32
+ # Tells Thor to use a non-zero exit code on errors and silences the related deprecation warning.
33
+ def self.exit_on_failure?
34
+ true
35
+ end
36
+
37
+ default_task :start
38
+
39
+ desc "start", "Starts Lita"
40
+ option :config,
41
+ aliases: "-c",
42
+ banner: "PATH",
43
+ default: File.expand_path("lita_config.rb", Dir.pwd),
44
+ desc: "Path to the configuration file to use"
45
+ option :log_file,
46
+ aliases: "-l",
47
+ banner: "PATH",
48
+ default: file_path_for("lita.log", "/var/log"),
49
+ desc: "Path where the log file should be written when daemonized"
50
+ option :pid_file,
51
+ aliases: "-p",
52
+ banner: "PATH",
53
+ default: file_path_for("lita.pid", "/var/run"),
54
+ desc: "Path where the PID file should be written when daemonized"
55
+ option :kill,
56
+ aliases: "-k",
57
+ default: false,
58
+ desc: "Kill existing Lita processes when starting the daemon",
59
+ type: :boolean
60
+ # Starts Lita.
61
+ # @return [void]
62
+ def start
63
+ begin
64
+ Bundler.require
65
+ rescue Bundler::GemfileNotFound
66
+ say I18n.t("lita.cli.no_gemfile_warning"), :red
67
+ exit(false)
68
+ end
69
+
70
+ Lita.run(options[:config])
71
+ end
72
+
73
+ desc "new NAME", "Generates a new Lita project (default name: lita)"
74
+ # Generates a new Lita project.
75
+ # @param name [String] The directory name for the new project.
76
+ # @return [void]
77
+ def new(name = "lita")
78
+ directory "robot", name
79
+ end
80
+
81
+ desc "adapter NAME", "Generates a new Lita adapter"
82
+ # Generates a new Lita adapter.
83
+ # @param name [String] The name for the new adapter.
84
+ # @return [void]
85
+ def adapter(name)
86
+ config = generate_config(name, "adapter")
87
+ generate_templates(config)
88
+ post_messages(config)
89
+ end
90
+
91
+ desc "handler NAME", "Generates a new Lita handler"
92
+ # Generates a new Lita handler.
93
+ # @param name [String] The name for the new handler.
94
+ # @return [void]
95
+ def handler(name)
96
+ config = generate_config(name, "handler")
97
+ generate_templates(config)
98
+ post_messages(config)
99
+ end
100
+
101
+ desc "extension NAME", "Generates a new Lita extension"
102
+ # Generates a new Lita extension.
103
+ # @param name [String] The name for the new extension.
104
+ # @return [void]
105
+ def extension(name)
106
+ config = generate_config(name, "extension")
107
+ generate_templates(config)
108
+ post_messages(config)
109
+ end
110
+
111
+ desc "version", "Outputs the current version of Lita"
112
+ # Outputs the current version of Lita.
113
+ # @return [void]
114
+ def version
115
+ puts VERSION
116
+ end
117
+ map %w[-v --version] => :version
118
+
119
+ desc "validate", "Verifies if lita is correctly configured"
120
+ option :config,
121
+ aliases: "-c",
122
+ banner: "PATH",
123
+ default: File.expand_path("lita_config.rb", Dir.pwd),
124
+ desc: "Path to the configuration file to use"
125
+ # Outputs detailed stacktrace when there is a problem or exit 0 when OK.
126
+ # You can use this as a pre-check script for any automation
127
+ # @return [void]
128
+ def validate
129
+ begin
130
+ Bundler.require
131
+ rescue Bundler::GemfileNotFound
132
+ say I18n.t("lita.cli.no_gemfile_warning"), :red
133
+ exit(false)
134
+ end
135
+
136
+ Lita.load_config(options[:config])
137
+ end
138
+
139
+ private
140
+
141
+ def generate_config(name, plugin_type)
142
+ name, gem_name = normalize_names(name)
143
+ constant_name = name.split(/_/).map(&:capitalize).join
144
+ namespace = "#{plugin_type}s"
145
+ constant_namespace = namespace.capitalize
146
+ spec_type = plugin_type == "handler" ? "lita_handler" : "lita"
147
+ required_lita_version = Lita::VERSION.split(/\./)[0...-1].join(".")
148
+
149
+ {
150
+ name: name,
151
+ gem_name: gem_name,
152
+ constant_name: constant_name,
153
+ plugin_type: plugin_type,
154
+ namespace: namespace,
155
+ constant_namespace: constant_namespace,
156
+ spec_type: spec_type,
157
+ required_lita_version: required_lita_version
158
+ }.merge(generate_user_config)
159
+ end
160
+
161
+ def generate_user_config
162
+ git_user = `git config user.name`.to_s.chomp
163
+ git_user = "TODO: Write your name" if git_user.empty?
164
+ git_email = `git config user.email`.to_s.chomp
165
+ git_email = "TODO: Write your email address" if git_email.empty?
166
+
167
+ {
168
+ author: git_user,
169
+ email: git_email
170
+ }
171
+ end
172
+
173
+ def generate_templates(config)
174
+ name = config[:name]
175
+ gem_name = config[:gem_name]
176
+ namespace = config[:namespace]
177
+
178
+ target = File.join(Dir.pwd, gem_name)
179
+
180
+ template(
181
+ "plugin/lib/lita/plugin_type/plugin.tt",
182
+ "#{target}/lib/lita/#{namespace}/#{name}.rb",
183
+ config
184
+ )
185
+ template("plugin/lib/plugin.tt", "#{target}/lib/#{gem_name}.rb", config)
186
+ template(
187
+ "plugin/spec/lita/plugin_type/plugin_spec.tt",
188
+ "#{target}/spec/lita/#{namespace}/#{name}_spec.rb",
189
+ config
190
+ )
191
+ template("plugin/spec/spec_helper.tt", "#{target}/spec/spec_helper.rb", config)
192
+ template("plugin/locales/en.yml.tt", "#{target}/locales/en.yml", config)
193
+ if config[:plugin_type] == "handler"
194
+ copy_file("plugin/templates/gitkeep", "#{target}/templates/.gitkeep")
195
+ end
196
+ copy_file("plugin/Gemfile", "#{target}/Gemfile")
197
+ template("plugin/gemspec.tt", "#{target}/#{gem_name}.gemspec", config)
198
+ copy_file("plugin/gitignore", "#{target}/.gitignore")
199
+ copy_file("plugin/Rakefile", "#{target}/Rakefile")
200
+ template("plugin/README.tt", "#{target}/README.md", config)
201
+ end
202
+
203
+ def license_message
204
+ say I18n.t("lita.cli.license_notice"), :yellow
205
+ end
206
+
207
+ def normalize_names(name)
208
+ name = name.downcase.sub(/^lita[_-]/, "")
209
+ gem_name = "lita-#{name}"
210
+ name = name.tr("-", "_")
211
+ [name, gem_name]
212
+ end
213
+
214
+ def post_messages
215
+ license_message
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "configuration_builder"
4
+
5
+ module Lita
6
+ # Mixin to add the ability for a plugin to define configuration.
7
+ # @since 4.0.0
8
+ module Configurable
9
+ # A block to be executed after configuration is finalized.
10
+ # @return [#call, nil] The block.
11
+ # @since 5.0.0
12
+ # @api private
13
+ attr_accessor :after_config_block
14
+
15
+ # The plugins's {ConfigurationBuilder} object.
16
+ # @return [ConfigurationBuilder] The configuration builder.
17
+ # @since 4.0.0
18
+ # @api public
19
+ attr_accessor :configuration_builder
20
+
21
+ # Registers a block to be executed after configuration is finalized.
22
+ # @yieldparam config [Configuration] The handler's configuration object.
23
+ # @return [void]
24
+ # @since 5.0.0
25
+ def after_config(&block)
26
+ self.after_config_block = block
27
+ end
28
+
29
+ # Sets a configuration attribute on the plugin.
30
+ # @return [void]
31
+ # @since 4.0.0
32
+ # @see ConfigurationBuilder#config
33
+ def config(*args, **kwargs, &block)
34
+ if block
35
+ configuration_builder.config(*args, **kwargs, &block)
36
+ else
37
+ configuration_builder.config(*args, **kwargs)
38
+ end
39
+ end
40
+
41
+ # Initializes the configuration builder for any inheriting classes.
42
+ def inherited(klass)
43
+ super
44
+ klass.configuration_builder = ConfigurationBuilder.new
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,247 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ice_nine"
4
+ require "i18n"
5
+
6
+ require_relative "errors"
7
+
8
+ module Lita
9
+ # An object that stores user settings to control Lita's behavior.
10
+ # @since 4.0.0
11
+ class Configuration; end
12
+
13
+ # Provides a DSL for building {Configuration} objects.
14
+ # @since 4.0.0
15
+ class ConfigurationBuilder
16
+ # An array of any nested configuration builders.
17
+ # @return [Array<ConfigurationBuilder>] The array of child configuration builders.
18
+ # @api private
19
+ attr_reader :children
20
+
21
+ # An array of valid types for the attribute.
22
+ # @return [Array<Object>] The array of valid types.
23
+ # @api private
24
+ attr_reader :types
25
+
26
+ # A block used to validate the attribute.
27
+ # @return [Proc] The validation block.
28
+ # @api private
29
+ attr_reader :validator
30
+
31
+ # The name of the configuration attribute.
32
+ # @return [String, Symbol] The attribute's name.
33
+ # @api private
34
+ attr_accessor :name
35
+
36
+ # The default value of the configuration attribute.
37
+ # @return [Object] The attribute's default value.
38
+ # @api private
39
+ attr_reader :default_value
40
+
41
+ # A boolean indicating whether or not the attribute must be set.
42
+ # @return [Boolean] Whether or not the attribute is required.
43
+ # @api private
44
+ attr_accessor :required
45
+ alias required? required
46
+
47
+ class << self
48
+ # Deeply freezes a configuration object so that it can no longer be modified.
49
+ # @param config [Configuration] The configuration object to freeze.
50
+ # @return [void]
51
+ # @api private
52
+ def freeze_config(config)
53
+ IceNine.deep_freeze!(config)
54
+ end
55
+
56
+ # Loads configuration from a user configuration file.
57
+ # @param config_path [String] The path to the configuration file.
58
+ # @return [void]
59
+ # @api private
60
+ def load_user_config(config_path = nil)
61
+ config_path ||= "lita_config.rb"
62
+
63
+ if File.exist?(config_path)
64
+ begin
65
+ load(config_path)
66
+ rescue ValidationError
67
+ exit(false)
68
+ rescue StandardError => e
69
+ Lita.logger.fatal I18n.t(
70
+ "lita.config.exception",
71
+ message: e.message,
72
+ backtrace: e.backtrace.join("\n")
73
+ )
74
+ exit(false)
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ def initialize
81
+ @children = []
82
+ @name = :root
83
+ end
84
+
85
+ # Builds a {Configuration} object from the attributes defined on the builder.
86
+ # @param object [Configuration] The empty configuration object that will be extended to
87
+ # create the final form.
88
+ # @return [Configuration] The fully built configuration object.
89
+ # @api private
90
+ def build(object = Configuration.new)
91
+ container = if children.empty?
92
+ build_leaf(object)
93
+ else
94
+ build_nested(object)
95
+ end
96
+
97
+ container.public_send(name)
98
+ end
99
+
100
+ # Returns a boolean indicating whether or not the attribute has any child attributes.
101
+ # @return [Boolean] Whether or not the attribute has any child attributes.
102
+ # @api private
103
+ def children?
104
+ !children.empty?
105
+ end
106
+
107
+ # Merges two configuration builders by making one an attribute on the other.
108
+ # @param name [String, Symbol] The name of the new attribute.
109
+ # @param attribute [ConfigurationBuilder] The configuration builder that should be its
110
+ # value.
111
+ # @return [void]
112
+ # @api private
113
+ def combine(name, attribute)
114
+ attribute.name = name
115
+
116
+ children << attribute
117
+ end
118
+
119
+ # Declares a configuration attribute.
120
+ # @param name [String, Symbol] The attribute's name.
121
+ # @param types [Object, Array<Object>] Optional: One or more types that the attribute's value
122
+ # must be.
123
+ # @param type [Object, Array<Object>] Optional: One or more types that the attribute's value
124
+ # must be.
125
+ # @param required [Boolean] Whether or not this attribute must be set. If required, and Lita
126
+ # is run without it set, Lita will abort on start up with a message about it.
127
+ # @param default [Object] An optional default value for the attribute.
128
+ # @yield A block to be evaluated in the context of the new attribute. Used for
129
+ # defining nested configuration attributes and validators.
130
+ # @return [void]
131
+ def config(name, types: nil, type: nil, required: false, default: nil, &block)
132
+ attribute = self.class.new
133
+ attribute.name = name
134
+ attribute.types = types || type
135
+ attribute.required = required
136
+ attribute.default_value = default
137
+ attribute.instance_exec(&block) if block
138
+
139
+ children << attribute
140
+ end
141
+
142
+ # Sets the valid types for the configuration attribute.
143
+ # @param types [Object, Array<Object>] One or more valid types.
144
+ # @return [void]
145
+ # @api private
146
+ def types=(types)
147
+ @types = Array(types) if types
148
+ end
149
+
150
+ # Declares a block to be used to validate the value of an attribute whenever it's set.
151
+ # Validation blocks should return any object to indicate an error, or +nil+/+false+ if
152
+ # validation passed. A configuration attribute can only have one validator, so if this method
153
+ # is called multiple times, only the last invocation will be used as the validator.
154
+ # @yield The code that performs validation.
155
+ # @return [void]
156
+ def validate(&block)
157
+ validator = block
158
+
159
+ # Fail loudly if the default value does not satisfy the validator.
160
+ unless default_value.nil?
161
+ error = validator.call(default_value)
162
+ raise ValidationError, error if error
163
+ end
164
+
165
+ @validator = block
166
+ end
167
+
168
+ # Sets the default value of the attribute, raising an error if it is not among the valid types.
169
+ # @param value [Object] The new value of the attribute.
170
+ # @return [void]
171
+ # @raise [TypeError] If the new value is not among the declared valid types.
172
+ # @api private
173
+ def default_value=(value)
174
+ ensure_valid_default_value(value)
175
+
176
+ @default_value = value
177
+ end
178
+
179
+ private
180
+
181
+ # Finalize a nested object.
182
+ def build_leaf(object)
183
+ this = self
184
+ run_validator = method(:run_validator)
185
+ check_types = method(:check_types)
186
+
187
+ # Define the accessors.
188
+ object.instance_exec do
189
+ define_singleton_method(this.name) { instance_variable_get("@#{this.name}") }
190
+ define_singleton_method("#{this.name}=") do |value|
191
+ run_validator.call(value)
192
+ check_types.call(value)
193
+ instance_variable_set("@#{this.name}", value)
194
+ end
195
+ end
196
+
197
+ # Set the default value without triggering the attr_writer's checks.
198
+ object.instance_variable_set("@#{this.name}", default_value)
199
+
200
+ object
201
+ end
202
+
203
+ # Finalize the root builder or any builder with children.
204
+ def build_nested(object)
205
+ this = self
206
+
207
+ nested_object = Configuration.new
208
+ children.each { |child| child.build(nested_object) }
209
+ object.instance_exec { define_singleton_method(this.name) { nested_object } }
210
+
211
+ object
212
+ end
213
+
214
+ # Check's the value's type from inside the finalized object.
215
+ def check_types(value)
216
+ if types&.none? { |type| value.is_a?(type) }
217
+ Lita.logger.fatal(
218
+ I18n.t("lita.config.type_error", attribute: name, types: types.join(", "))
219
+ )
220
+
221
+ raise ValidationError
222
+ end
223
+ end
224
+
225
+ # Raise if value is non-nil and isn't one of the specified types.
226
+ def ensure_valid_default_value(value)
227
+ if !value.nil? && types&.none? { |type| value.is_a?(type) }
228
+ raise TypeError, I18n.t("lita.config.type_error", attribute: name, types: types.join(", "))
229
+ end
230
+ end
231
+
232
+ # Runs the validator from inside the build configuration object.
233
+ def run_validator(value)
234
+ return unless validator
235
+
236
+ error = validator.call(value)
237
+
238
+ if error
239
+ Lita.logger.fatal(
240
+ I18n.t("lita.config.validation_error", attribute: name, message: error)
241
+ )
242
+
243
+ raise ValidationError
244
+ end
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lita
4
+ # Validates a registry's configuration, checking for required attributes that are missing.
5
+ # @since 4.0.0
6
+ # @api private
7
+ class ConfigurationValidator
8
+ # @param registry [Registry] The registry containing the configuration to validate.
9
+ def initialize(registry)
10
+ self.registry = registry
11
+ end
12
+
13
+ # Validates adapter and handler configuration. Logs a fatal warning and aborts if any required
14
+ # configuration attributes are missing.
15
+ # @return [void]
16
+ def call
17
+ validate_adapters
18
+ validate_handlers
19
+ end
20
+
21
+ private
22
+
23
+ # The registry containing the configuration to validate.
24
+ attr_accessor :registry
25
+
26
+ # The registry's adapters.
27
+ def adapters
28
+ registry.adapters
29
+ end
30
+
31
+ # All a plugin's top-level configuration attributes.
32
+ def children_for(plugin)
33
+ plugin.configuration_builder.children
34
+ end
35
+
36
+ # Return the {Configuration} for the given plugin.
37
+ # @param type [String, Symbol] Either "adapters" or "handlers".
38
+ # @param name [String, Symbol] The name of the plugin's top-level {Configuration}.
39
+ # @param namespace [Array<String, Symbol>] A list of nested config attributes to traverse to
40
+ # find the desired {Configuration}.
41
+ def config_for(type, name, namespace)
42
+ config = registry.config.public_send(type).public_send(name)
43
+ namespace.each { |n| config = config.public_send(n) }
44
+ config
45
+ end
46
+
47
+ # Generates the fully qualified name of a configuration attribute.
48
+ def full_attribute_name(names, name)
49
+ (names + [name]).join(".")
50
+ end
51
+
52
+ # The registry's handlers.
53
+ def handlers
54
+ registry.handlers
55
+ end
56
+
57
+ # Validates the registry's adapters.
58
+ def validate_adapters
59
+ adapters.each do |adapter_name, adapter|
60
+ validate(:adapter, adapter_name, adapter, children_for(adapter))
61
+ end
62
+ end
63
+
64
+ # Validates the registry's handlers.
65
+ def validate_handlers
66
+ handlers.each do |handler|
67
+ validate(:handler, handler.namespace, handler, children_for(handler))
68
+ end
69
+ end
70
+
71
+ # Validates an array of attributes, recursing if any nested attributes are encountered.
72
+ def validate(type, plugin_name, plugin, attributes, attribute_namespace = [])
73
+ attributes.each do |attribute|
74
+ config = config_for("#{type}s", plugin_name, attribute_namespace)
75
+
76
+ if attribute.children?
77
+ validate(
78
+ type,
79
+ plugin_name,
80
+ plugin,
81
+ attribute.children,
82
+ attribute_namespace.clone.push(attribute.name),
83
+ )
84
+ elsif attribute.required? && config.public_send(attribute.name).nil?
85
+ Lita.logger.fatal I18n.t(
86
+ "lita.config.missing_required_#{type}_attribute",
87
+ type => plugin_name,
88
+ attribute: full_attribute_name(attribute_namespace, attribute.name)
89
+ )
90
+ exit(false)
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end