lita 3.3.1 → 4.0.0.rc1

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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/.travis.yml +3 -0
  4. data/lib/lita.rb +45 -97
  5. data/lib/lita/adapter.rb +38 -17
  6. data/lib/lita/adapters/shell.rb +5 -3
  7. data/lib/lita/authorization.rb +109 -60
  8. data/lib/lita/builder.rb +38 -0
  9. data/lib/lita/callback.rb +37 -0
  10. data/lib/lita/cli.rb +2 -0
  11. data/lib/lita/config.rb +1 -18
  12. data/lib/lita/configurable.rb +29 -0
  13. data/lib/lita/configuration.rb +179 -0
  14. data/lib/lita/configuration_validator.rb +66 -0
  15. data/lib/lita/daemon.rb +4 -11
  16. data/lib/lita/default_configuration.rb +146 -0
  17. data/lib/lita/errors.rb +9 -0
  18. data/lib/lita/handler.rb +5 -264
  19. data/lib/lita/handler/chat_router.rb +130 -0
  20. data/lib/lita/handler/common.rb +114 -0
  21. data/lib/lita/handler/event_router.rb +77 -0
  22. data/lib/lita/handler/http_router.rb +26 -0
  23. data/lib/lita/handlers/authorization.rb +13 -18
  24. data/lib/lita/handlers/deprecation_check.rb +24 -0
  25. data/lib/lita/handlers/help.rb +5 -3
  26. data/lib/lita/handlers/info.rb +2 -2
  27. data/lib/lita/http_callback.rb +29 -0
  28. data/lib/lita/http_route.rb +41 -26
  29. data/lib/lita/namespace.rb +23 -0
  30. data/lib/lita/rack_app.rb +29 -2
  31. data/lib/lita/registry.rb +133 -0
  32. data/lib/lita/robot.rb +58 -20
  33. data/lib/lita/route_validator.rb +12 -4
  34. data/lib/lita/rspec.rb +23 -14
  35. data/lib/lita/rspec/handler.rb +93 -23
  36. data/lib/lita/rspec/matchers/chat_route_matcher.rb +48 -0
  37. data/lib/lita/rspec/matchers/deprecated.rb +36 -0
  38. data/lib/lita/rspec/matchers/event_route_matcher.rb +27 -0
  39. data/lib/lita/rspec/matchers/http_route_matcher.rb +18 -60
  40. data/lib/lita/user.rb +0 -6
  41. data/lib/lita/util.rb +1 -8
  42. data/lib/lita/version.rb +1 -1
  43. data/lita.gemspec +1 -0
  44. data/spec/lita/adapter_spec.rb +25 -7
  45. data/spec/lita/adapters/shell_spec.rb +24 -4
  46. data/spec/lita/authorization_spec.rb +57 -38
  47. data/spec/lita/builder_spec.rb +39 -0
  48. data/spec/lita/config_spec.rb +0 -24
  49. data/spec/lita/configuration_spec.rb +222 -0
  50. data/spec/lita/configuration_validator_spec.rb +112 -0
  51. data/spec/lita/daemon_spec.rb +2 -2
  52. data/spec/lita/default_configuration_spec.rb +254 -0
  53. data/spec/lita/handler/chat_router_spec.rb +192 -0
  54. data/spec/lita/handler/common_spec.rb +272 -0
  55. data/spec/lita/handler/event_router_spec.rb +54 -0
  56. data/spec/lita/handler/http_router_spec.rb +106 -0
  57. data/spec/lita/handler_spec.rb +20 -291
  58. data/spec/lita/handlers/authorization_spec.rb +9 -11
  59. data/spec/lita/handlers/deprecation_check_spec.rb +21 -0
  60. data/spec/lita/handlers/help_spec.rb +31 -9
  61. data/spec/lita/handlers/info_spec.rb +2 -2
  62. data/spec/lita/handlers/room_spec.rb +5 -3
  63. data/spec/lita/robot_spec.rb +30 -11
  64. data/spec/lita/rspec_spec.rb +71 -31
  65. data/spec/lita/user_spec.rb +2 -2
  66. data/spec/lita_spec.rb +62 -4
  67. data/spec/spec_helper.rb +7 -0
  68. data/templates/locales/en.yml +56 -4
  69. data/templates/plugin/gemspec.tt +1 -0
  70. data/templates/plugin/spec/spec_helper.tt +4 -0
  71. metadata +54 -8
  72. data/lib/lita/rspec/matchers/event_subscription_matcher.rb +0 -67
  73. data/lib/lita/rspec/matchers/route_matcher.rb +0 -69
  74. data/spec/lita/rack_app_spec.rb +0 -92
@@ -0,0 +1,38 @@
1
+ module Lita
2
+ # Constructs a Lita plugin from a block.
3
+ # @since 4.0.0
4
+ # @api private
5
+ class Builder
6
+ # @param namespace [String, Symbol] The Redis namespace to use for the plugin.
7
+ # @yield The class body of the plugin.
8
+ def initialize(namespace, &block)
9
+ @namespace = namespace.to_s
10
+ @block = block
11
+ end
12
+
13
+ # Constructs an {Lita::Adapter} from the provided block.
14
+ # @return [Lita::Adapter]
15
+ def build_adapter
16
+ adapter = create_plugin(Adapter)
17
+ adapter.class_exec(&@block)
18
+ adapter
19
+ end
20
+
21
+ # Constructs a {Lita::Handler} from the provided block.
22
+ # @return [Lita::Handler]
23
+ def build_handler
24
+ handler = create_plugin(Handler)
25
+ handler.class_exec(&@block)
26
+ handler
27
+ end
28
+
29
+ private
30
+
31
+ # Creates a class of the relevant plugin type and sets its namespace.
32
+ def create_plugin(plugin_type)
33
+ plugin = Class.new(plugin_type)
34
+ plugin.namespace(@namespace)
35
+ plugin
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,37 @@
1
+ module Lita
2
+ # Represents the action that is taken when a route or event is triggered. It
3
+ # can be a block or the name of a method on object.
4
+ # @since 4.0.0
5
+ # @api private
6
+ class Callback
7
+ # A block that should be used as the callback.
8
+ attr_reader :block
9
+
10
+ # The name of the method in the plugin that should be called as the callback.
11
+ attr_reader :method_name
12
+
13
+ # @overload initialize(method_name)
14
+ # @param method_name [String, Symbol] The name of the instance method that serves as the
15
+ # callback.
16
+ # @overload initialize(callable)
17
+ # @param callable [Proc] A callable object to use as the callback.
18
+ def initialize(method_name_or_callable)
19
+ if method_name_or_callable.respond_to?(:call)
20
+ @block = method_name_or_callable
21
+ else
22
+ @method_name = method_name_or_callable
23
+ end
24
+ end
25
+
26
+ # Invokes the callback.
27
+ def call(host, *args)
28
+ if block
29
+ host.instance_exec(*args, &block)
30
+ else
31
+ host.public_send(method_name, *args)
32
+ end
33
+
34
+ true
35
+ end
36
+ end
37
+ end
@@ -65,6 +65,8 @@ module Lita
65
65
  end
66
66
 
67
67
  if options[:daemonize]
68
+ say I18n.t("lita.cli.daemon_deprecated"), :red
69
+
68
70
  Daemon.new(
69
71
  options[:pid_file],
70
72
  options[:log_file],
@@ -1,5 +1,6 @@
1
1
  module Lita
2
2
  # An object that stores various user settings that affect Lita's behavior.
3
+ # @deprecated Will be removed in Lita 5.0. Use {Lita::Configuration} instead.
3
4
  class Config < Hash
4
5
  class << self
5
6
  # Initializes a new Config object with the default settings.
@@ -15,24 +16,6 @@ module Lita
15
16
  end
16
17
  end
17
18
 
18
- # Loads configuration from a user configuration file.
19
- # @param config_path [String] The path to the configuration file.
20
- # @return [void]
21
- def load_user_config(config_path = nil)
22
- config_path = "lita_config.rb" unless config_path
23
-
24
- begin
25
- load(config_path)
26
- rescue Exception => e
27
- Lita.logger.fatal I18n.t(
28
- "lita.config.exception",
29
- message: e.message,
30
- backtrace: e.backtrace.join("\n")
31
- )
32
- abort
33
- end if File.exist?(config_path)
34
- end
35
-
36
19
  private
37
20
 
38
21
  # Adds and populates a Config object to Lita.config.handlers for every
@@ -0,0 +1,29 @@
1
+ module Lita
2
+ # Mixin to add the ability for a plugin to define configuration.
3
+ # @since 4.0.0
4
+ # @api private
5
+ module Configurable
6
+ # The plugins's {Configuration} object.
7
+ # @return [Lita::Configuration] The configuration object.
8
+ # @since 4.0.0
9
+ attr_accessor :configuration
10
+
11
+ # Sets a configuration attribute on the plugin.
12
+ # @return [void]
13
+ # @since 4.0.0
14
+ # @see Lita::Configuration#config
15
+ def config(*args, **kwargs)
16
+ if block_given?
17
+ configuration.config(*args, **kwargs, &proc)
18
+ else
19
+ configuration.config(*args, **kwargs)
20
+ end
21
+ end
22
+
23
+ # Initializes the configuration object for any inheriting classes.
24
+ def inherited(klass)
25
+ super
26
+ klass.configuration = Configuration.new
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,179 @@
1
+ module Lita
2
+ # An object that stores various settings that affect Lita's behavior.
3
+ # @since 4.0.0
4
+ class Configuration
5
+ # An array of any nested configuration objects.
6
+ # @return [Array<Lita::Configuration>] The array of child configuration objects.
7
+ attr_reader :children
8
+
9
+ # An array of valid types for the attribute.
10
+ # @return [Array<Object>] The array of valid types.
11
+ attr_reader :types
12
+
13
+ # A block used to validate the attribute.
14
+ # @return [Proc] The validation block.
15
+ attr_reader :validator
16
+
17
+ # The name of the configuration attribute.
18
+ # @return [String, Symbol] The attribute's name.
19
+ attr_accessor :name
20
+
21
+ # The value of the configuration attribute.
22
+ # @return [Object] The attribute's value.
23
+ attr_accessor :value
24
+
25
+ # A boolean indicating whether or not the attribute must be set.
26
+ # @return [Boolean] Whether or not the attribute is required.
27
+ attr_accessor :required
28
+ alias_method :required?, :required
29
+
30
+ class << self
31
+ # Deeply freezes a configuration object so that it can no longer be modified.
32
+ # @param config [Lita::Configuration] The configuration object to freeze.
33
+ # @return [void]
34
+ def freeze_config(config)
35
+ IceNine.deep_freeze!(config)
36
+ end
37
+
38
+ # Loads configuration from a user configuration file.
39
+ # @param config_path [String] The path to the configuration file.
40
+ # @return [void]
41
+ def load_user_config(config_path = nil)
42
+ config_path = "lita_config.rb" unless config_path
43
+
44
+ begin
45
+ load(config_path)
46
+ rescue Exception => e
47
+ Lita.logger.fatal I18n.t(
48
+ "lita.config.exception",
49
+ message: e.message,
50
+ backtrace: e.backtrace.join("\n")
51
+ )
52
+ abort
53
+ end if File.exist?(config_path)
54
+ end
55
+ end
56
+
57
+ def initialize
58
+ @children = []
59
+ @name = :root
60
+ end
61
+
62
+ # Returns a boolean indicating whether or not the attribute has any child attributes.
63
+ # @return [Boolean] Whether or not the attribute has any child attributes.
64
+ def children?
65
+ !children.empty?
66
+ end
67
+
68
+ # Merges two configuration objects by making one an attribute on the other.
69
+ # @param name [String, Symbol] The name of the new attribute.
70
+ # @param attribute [Lita::Configuration] The configuration object that should be its value.
71
+ # @return [void]
72
+ def combine(name, attribute)
73
+ attribute.name = name
74
+
75
+ children << attribute
76
+ end
77
+
78
+ # Declares a configuration attribute.
79
+ # @param name [String, Symbol] The attribute's name.
80
+ # @param types [Object, Array<Object>] Optional: One or more types that the attribute's value
81
+ # must be.
82
+ # @param type [Object, Array<Object>] Optional: One or more types that the attribute's value
83
+ # must be.
84
+ # @param required [Boolean] Whether or not this attribute must be set. If required, and Lita
85
+ # is run without it set, a {Lita::ValidationError} will be raised.
86
+ # @param default [Object] An optional default value for the attribute.
87
+ # @yield A block to be evaluated in the context of the new attribute. Used for
88
+ # defining nested configuration attributes and validators.
89
+ # @return [void]
90
+ def config(name, types: nil, type: nil, required: false, default: nil)
91
+ attribute = self.class.new
92
+ attribute.name = name
93
+ attribute.types = types || type
94
+ attribute.required = required
95
+ attribute.value = default
96
+ attribute.instance_exec(&proc) if block_given?
97
+
98
+ children << attribute
99
+ end
100
+
101
+ # Extracts the finalized configuration object as it will be interacted with by the user.
102
+ # @param object [Object] The bare object that will be extended to create the final form.
103
+ # @return [Object] A bare object with only the methods that were declared via the
104
+ # {Lita::Configuration} DSL.
105
+ def finalize(object = Object.new)
106
+ container = if children.empty?
107
+ finalize_simple(object)
108
+ else
109
+ finalize_nested(object)
110
+ end
111
+
112
+ container.public_send(name)
113
+ end
114
+
115
+ # Sets the valid types for the configuration attribute.
116
+ # @param types [Object, Array<Object>] One or more valid types.
117
+ # @return [void]
118
+ def types=(types)
119
+ @types = Array(types) if types
120
+ end
121
+
122
+ # Declares a block to be used to validate the value of an attribute whenever it's set.
123
+ # Validation blocks should return any object to indicate an error, or +nil+/+false+ if
124
+ # validation passed.
125
+ # @yield The code that performs validation.
126
+ # @return [void]
127
+ def validate
128
+ @validator = proc
129
+ end
130
+
131
+ # Sets the value of the attribute, raising an error if it is not among the valid types.
132
+ # @param value [Object] The new value of the attribute.
133
+ # @return [void]
134
+ # @raise [TypeError] If the new value is not among the declared valid types.
135
+ def value=(value)
136
+ if value && types && types.none? { |type| type === value }
137
+ raise TypeError, "#{name} must be one of: #{types.inspect}"
138
+ end
139
+
140
+ @value = value
141
+ end
142
+
143
+ private
144
+
145
+ # Finalize the root object.
146
+ def finalize_nested(object)
147
+ this = self
148
+
149
+ nested_object = Object.new
150
+ children.each { |child| child.finalize(nested_object) }
151
+ object.instance_exec { define_singleton_method(this.name) { nested_object } }
152
+
153
+ object
154
+ end
155
+
156
+ # Finalize a nested object.
157
+ def finalize_simple(object)
158
+ this = self
159
+
160
+ object.instance_exec do
161
+ define_singleton_method(this.name) { this.value }
162
+ define_singleton_method("#{this.name}=") do |value|
163
+ if this.validator
164
+ error = this.validator.call(value)
165
+ raise ValidationError, error if error
166
+ end
167
+
168
+ if this.types && this.types.none? { |type| type === value }
169
+ raise TypeError, "#{this.name} must be one of: #{this.types.inspect}"
170
+ end
171
+
172
+ this.value = value
173
+ end
174
+ end
175
+
176
+ object
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,66 @@
1
+ module Lita
2
+ # Validates a registry's configuration, checking for required attributes that are missing.
3
+ # @since 4.0.0
4
+ # @api private
5
+ class ConfigurationValidator
6
+ # @param registry [Lita::Registry] The registry to validate.
7
+ def initialize(registry)
8
+ @registry = registry
9
+ end
10
+
11
+ # Validates adapter and handler configuration.
12
+ # @return [void]
13
+ # @raise [Lita::ValidationError] If any required configuration attributes are missing.
14
+ def call
15
+ validate_adapters
16
+ validate_handlers
17
+ end
18
+
19
+ private
20
+
21
+ # The registry's adapters.
22
+ def adapters
23
+ @registry.adapters
24
+ end
25
+
26
+ # All a plugin's top-level configuration attributes.
27
+ def children_for(plugin)
28
+ plugin.configuration.children
29
+ end
30
+
31
+ # Generates the fully qualified name of a configuration attribute.
32
+ def full_attribute_name(names, name)
33
+ (names + [name]).join(".")
34
+ end
35
+
36
+ # The registry's handlers.
37
+ def handlers
38
+ @registry.handlers
39
+ end
40
+
41
+ # Validates the registry's adapters.
42
+ def validate_adapters
43
+ adapters.each_value { |adapter| validate(:adapter, adapter, children_for(adapter)) }
44
+ end
45
+
46
+ # Validates the registry's handlers.
47
+ def validate_handlers
48
+ handlers.each { |handler| validate(:handler, handler, children_for(handler)) }
49
+ end
50
+
51
+ # Validates an array of attributes, recursing if any nested attributes are encountered.
52
+ def validate(type, plugin, attributes, attribute_namespace = [])
53
+ attributes.each do |attribute|
54
+ if attribute.children?
55
+ validate(type, plugin, attribute.children, attribute_namespace.clone.push(attribute.name))
56
+ elsif attribute.required? && attribute.value.nil?
57
+ raise ValidationError, I18n.t(
58
+ "lita.config.missing_required_#{type}_attribute",
59
+ type => plugin.namespace,
60
+ attribute: full_attribute_name(attribute_namespace, attribute.name)
61
+ )
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -2,6 +2,7 @@ require "fileutils"
2
2
 
3
3
  module Lita
4
4
  # Converts Lita to a daemon process.
5
+ # @deprecated Will be removed in Lita 5.0. Use your operating system's process manager instead.
5
6
  class Daemon
6
7
  # @param pid_path [String] The path to the PID file.
7
8
  # @param log_path [String] The path to the log file.
@@ -49,17 +50,9 @@ module Lita
49
50
  # Redirect the standard streams to a log file.
50
51
  def set_up_logs
51
52
  log_file = File.new(@log_path, "a")
52
- stdout.reopen(log_file)
53
- stderr.reopen(log_file)
54
- stderr.sync = stdout.sync = true
55
- end
56
-
57
- def stdout
58
- $stdout
59
- end
60
-
61
- def stderr
62
- $stderr
53
+ STDOUT.reopen(log_file)
54
+ STDERR.reopen(log_file)
55
+ STDERR.sync = STDOUT.sync = true
63
56
  end
64
57
  end
65
58
  end
@@ -0,0 +1,146 @@
1
+ module Lita
2
+ # Builds the configuration object that is stored in each {Lita::Registry}.
3
+ # @since 4.0.0
4
+ # @api private
5
+ class DefaultConfiguration
6
+ # Valid levels for Lita's logger.
7
+ LOG_LEVELS = %w(debug info warn error fatal)
8
+
9
+ # A {Registry} to extract configuration for plugins from.
10
+ # @return [Lita::Registry] The registry.
11
+ attr_reader :registry
12
+
13
+ # The top-level {Configuration} attribute.
14
+ # @return [Lita::Configuration] The root attribute.
15
+ attr_reader :root
16
+
17
+ # @param registry [Lita::Registry] The registry to build a default configuration object from.
18
+ def initialize(registry)
19
+ @registry = registry
20
+ @root = Configuration.new
21
+
22
+ adapters_config
23
+ handlers_config
24
+ http_config
25
+ redis_config
26
+ robot_config
27
+ end
28
+
29
+ # Processes the {Configuration} object to return a raw object with only the appropriate methods.
30
+ # This is the value that's actually stored in {Lita::Registry#config}.
31
+ # @return [Object] The final form of the configuration object.
32
+ def finalize
33
+ final_config = root.finalize
34
+ add_adapter_attribute(final_config)
35
+ add_struct_access_to_redis(final_config.redis)
36
+ final_config
37
+ end
38
+
39
+ private
40
+
41
+ # Builds config.adapters
42
+ def adapters_config
43
+ adapters = registry.adapters
44
+
45
+ root.config :adapters do
46
+ adapters.each do |key, adapter|
47
+ combine(key, adapter.configuration)
48
+ end
49
+ end
50
+ end
51
+
52
+ # Builds config.adapter
53
+ def add_adapter_attribute(config)
54
+ config.singleton_class.class_exec { attr_accessor :adapter }
55
+ config.adapter = Class.new(Config) do
56
+ def []=(key, value)
57
+ deprecation_warning
58
+
59
+ super
60
+ end
61
+
62
+ def [](key)
63
+ deprecation_warning
64
+
65
+ super
66
+ end
67
+
68
+ def method_missing(name, *args)
69
+ deprecation_warning
70
+
71
+ super
72
+ end
73
+
74
+ def deprecation_warning
75
+ Lita.logger.warn(I18n.t("lita.config.adapter_deprecated"))
76
+ end
77
+ private :deprecation_warning
78
+ end.new
79
+ end
80
+
81
+ # Allow config.redis to be accessed as a struct, for backwards compatibility.
82
+ def add_struct_access_to_redis(redis)
83
+ def redis.method_missing(name, *args)
84
+ Lita.logger.warn(I18n.t("lita.config.redis_struct_access_deprecated"))
85
+ name_string = name.to_s
86
+ if name_string.chomp!("=")
87
+ self[name_string.to_sym] = args.first
88
+ else
89
+ self[name_string.to_sym]
90
+ end
91
+ end
92
+ end
93
+
94
+ # Builds config.handlers
95
+ def handlers_config
96
+ handlers = registry.handlers
97
+
98
+ root.config :handlers do
99
+ handlers.each do |handler|
100
+ if handler.configuration.children?
101
+ combine(handler.namespace, handler.configuration)
102
+ else
103
+ old_config = Config.new
104
+ handler.default_config(old_config) if handler.respond_to?(:default_config)
105
+ config(handler.namespace, default: old_config)
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ # Builds config.http
112
+ def http_config
113
+ root.config :http do
114
+ config :host, type: String, default: "0.0.0.0"
115
+ config :port, type: Integer, default: 8080
116
+ config :min_threads, type: Integer, default: 0
117
+ config :max_threads, type: Integer, default: 16
118
+ config :middleware, type: Array, default: []
119
+ end
120
+ end
121
+
122
+ # Builds config.redis
123
+ def redis_config
124
+ root.config :redis, type: Hash, default: {}
125
+ end
126
+
127
+ # Builds config.robot
128
+ def robot_config
129
+ root.config :robot do
130
+ config :name, type: String, default: "Lita"
131
+ config :mention_name, type: String
132
+ config :alias, type: String
133
+ config :adapter, types: [String, Symbol], default: :shell
134
+ config :locale, types: [String, Symbol], default: I18n.locale
135
+ config :log_level, types: [String, Symbol], default: :info do
136
+ validate do |value|
137
+ unless LOG_LEVELS.include?(value.to_s.downcase.strip)
138
+ "log_level must be one of: #{LOG_LEVELS.join(", ")}"
139
+ end
140
+ end
141
+ end
142
+ config :admins
143
+ end
144
+ end
145
+ end
146
+ end