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,9 @@
1
+ module Lita
2
+ # The root exception class that all Lita-specific exceptions inherit from.
3
+ # @since 4.0.0
4
+ class Error < StandardError; end
5
+
6
+ # An exception raised when a configuration attribute is invalid.
7
+ # @since 4.0.0
8
+ class ValidationError < Error; end
9
+ end
@@ -1,268 +1,9 @@
1
1
  module Lita
2
- # Base class for objects that add new behavior to Lita.
2
+ # Base class for objects that add new behavior to Lita. {Handler} is simply a class with all
3
+ # types of routers mixed in.
3
4
  class Handler
4
- extend Forwardable
5
-
6
- # A Redis::Namespace scoped to the handler.
7
- # @return [Redis::Namespace]
8
- attr_reader :redis
9
-
10
- # The running {Lita::Robot} instance.
11
- # @return [Lita::Robot]
12
- attr_reader :robot
13
-
14
- # A Struct representing a chat route defined by a handler.
15
- class Route < Struct.new(
16
- :pattern,
17
- :method_name,
18
- :command,
19
- :required_groups,
20
- :help,
21
- :extensions
22
- )
23
- alias_method :command?, :command
24
- end
25
-
26
- class << self
27
- # Creates a chat route.
28
- # @param pattern [Regexp] A regular expression to match incoming messages
29
- # against.
30
- # @param method [Symbol, String] The name of the method to trigger.
31
- # @param command [Boolean] Whether or not the message must be directed at
32
- # the robot.
33
- # @param restrict_to [Array<Symbol, String>, nil] A list of authorization
34
- # groups the user must be in to trigger the route.
35
- # @param help [Hash] A map of example invocations to descriptions.
36
- # @param extensions [Hash] Aribtrary additional data that can be used by Lita extensions.
37
- # @return [void]
38
- def route(pattern, method, **options)
39
- options = default_route_options.merge(options)
40
- options[:restrict_to] = options[:restrict_to].nil? ? nil : Array(options[:restrict_to])
41
- routes << Route.new(
42
- pattern,
43
- method,
44
- options.delete(:command),
45
- options.delete(:restrict_to),
46
- options.delete(:help),
47
- options
48
- )
49
- end
50
-
51
- # A list of chat routes defined by the handler.
52
- # @return [Array<Lita::Handler::Route>]
53
- def routes
54
- @routes ||= []
55
- end
56
-
57
- # The main entry point for the handler at runtime. Checks if the message
58
- # matches any of the routes and invokes the route's method if it does.
59
- # Called by {Lita::Robot#receive}.
60
- # @param robot [Lita::Robot] The currently running robot.
61
- # @param message [Lita::Message] The incoming message.
62
- # @return [void]
63
- def dispatch(robot, message)
64
- routes.each do |route|
65
- next unless route_applies?(route, message, robot)
66
- log_dispatch(route)
67
- dispatch_to_route(route, robot, message)
68
- end
69
- end
70
-
71
- # Dispatch directly to a {Route}, ignoring route conditions.
72
- # @param route [Route] The route to invoke.
73
- # @param robot [Lita::Robot] The currently running robot.
74
- # @param message [Lita::Message] The incoming message.
75
- # @return [void]
76
- # @since 3.3.0
77
- def dispatch_to_route(route, robot, message)
78
- response = Response.new(message, route.pattern)
79
- Lita.hooks[:trigger_route].each { |hook| hook.call(response: response, route: route) }
80
- new(robot).public_send(route.method_name, response)
81
- rescue Exception => e
82
- log_dispatch_error(e)
83
- raise e if rspec_loaded?
84
- end
85
-
86
- # Creates a new {Lita::HTTPRoute} which is used to define an HTTP route
87
- # for the built-in web server.
88
- # @see Lita::HTTPRoute
89
- # @return [Lita::HTTPRoute] The new {Lita::HTTPRoute}.
90
- def http
91
- HTTPRoute.new(self)
92
- end
93
-
94
- # An array of all HTTP routes defined for the handler.
95
- # @return [Array<Lita::HTTPRoute>] The array of routes.
96
- def http_routes
97
- @http_routes ||= []
98
- end
99
-
100
- # The namespace for the handler, used for its configuration object and
101
- # Redis store. If the handler is an anonymous class, it must explicitly
102
- # define +self.name+.
103
- # @return [String] The handler's namespace.
104
- # @raise [RuntimeError] If +self.name+ is not defined.
105
- def namespace
106
- if name
107
- Util.underscore(name.split("::").last)
108
- else
109
- raise I18n.t("lita.handler.name_required")
110
- end
111
- end
112
-
113
- # Registers an event subscription. When an event is triggered with
114
- # {trigger}, a new instance of the handler will be created and the
115
- # instance method name supplied to {on} will be invoked with a payload
116
- # (a hash of arbitrary keys and values).
117
- # @param event_name [String, Symbol] The name of the event to subscribe
118
- # to.
119
- # @param method_name [String, Symbol] The name of the instance method on
120
- # the handler that should be invoked when the event is triggered.
121
- # @return [void]
122
- def on(event_name, method_name)
123
- event_subscriptions[normalize_event(event_name)] << method_name
124
- end
125
-
126
- # Returns the translation for a key, automatically namespaced to the handler.
127
- # @param key [String] The key of the translation.
128
- # @param hash [Hash] An optional hash of values to be interpolated in the string.
129
- # @return [String] The translated string.
130
- # @since 3.0.0
131
- def translate(key, hash = {})
132
- I18n.translate("lita.handlers.#{namespace}.#{key}", hash)
133
- end
134
-
135
- alias_method :t, :translate
136
-
137
- # Triggers an event, invoking methods previously registered with {on} and
138
- # passing them a payload hash with any arbitrary data.
139
- # @param robot [Lita::Robot] The currently running robot instance.
140
- # @param event_name [String, Symbol], The name of the event to trigger.
141
- # @param payload [Hash] An optional hash of arbitrary data.
142
- # @return [void]
143
- def trigger(robot, event_name, payload = {})
144
- event_subscriptions[normalize_event(event_name)].each do |method_name|
145
- new(robot).public_send(method_name, payload)
146
- end
147
- end
148
-
149
- private
150
-
151
- def default_route_options
152
- {
153
- command: false,
154
- restrict_to: nil,
155
- help: {}
156
- }
157
- end
158
-
159
- # A hash of arrays used to store event subscriptions registered with {on}.
160
- def event_subscriptions
161
- @event_subscriptions ||= Hash.new { |h, k| h[k] = [] }
162
- end
163
-
164
- # Determines whether or not an incoming messages should trigger a route.
165
- def route_applies?(route, message, robot)
166
- RouteValidator.new(self, route, message, robot).call
167
- end
168
-
169
- # Checks if RSpec is loaded. If so, assume we are testing and let handler
170
- # exceptions bubble up.
171
- def rspec_loaded?
172
- defined?(::RSpec)
173
- end
174
-
175
- # Logs the dispatch of message.
176
- def log_dispatch(route)
177
- Lita.logger.debug I18n.t(
178
- "lita.handler.dispatch",
179
- handler: name,
180
- method: route.method_name
181
- )
182
- end
183
-
184
- def log_dispatch_error(e)
185
- Lita.logger.error I18n.t(
186
- "lita.handler.exception",
187
- handler: name,
188
- message: e.message,
189
- backtrace: e.backtrace.join("\n")
190
- )
191
- end
192
-
193
- def normalize_event(event_name)
194
- event_name.to_s.downcase.strip.to_sym
195
- end
196
- end
197
-
198
- # @param robot [Lita::Robot] The currently running robot.
199
- def initialize(robot)
200
- @robot = robot
201
- @redis = Redis::Namespace.new(redis_namespace, redis: Lita.redis)
202
- end
203
-
204
- # Invokes the given block after the given number of seconds.
205
- # @param interval [Integer] The number of seconds to wait before invoking the block.
206
- # @yieldparam timer [Lita::Timer] The current {Lita::Timer} instance.
207
- # @return [void]
208
- # @since 3.0.0
209
- def after(interval, &block)
210
- Thread.new { Timer.new(interval: interval, &block).start }
211
- end
212
-
213
- # The handler's config object.
214
- # @return [Lita::Config] The handler's config object.
215
- # @since 3.2.0
216
- def config
217
- Lita.config.handlers[self.class.namespace]
218
- end
219
-
220
- # Invokes the given block repeatedly, waiting the given number of seconds between each
221
- # invocation.
222
- # @param interval [Integer] The number of seconds to wait before each invocation of the block.
223
- # @yieldparam timer [Lita::Timer] The current {Lita::Timer} instance.
224
- # @return [void]
225
- # @note The block should call {Lita::Timer#stop} at a terminating condition to avoid infinite
226
- # recursion.
227
- # @since 3.0.0
228
- def every(interval, &block)
229
- Thread.new { Timer.new(interval: interval, recurring: true, &block).start }
230
- end
231
-
232
- # Creates a new +Faraday::Connection+ for making HTTP requests.
233
- # @param options [Hash] A set of options passed on to Faraday.
234
- # @yield [builder] A Faraday builder object for adding middleware.
235
- # @return [Faraday::Connection] The new connection object.
236
- def http(options = {}, &block)
237
- options = default_faraday_options.merge(options)
238
- Faraday::Connection.new(nil, options, &block)
239
- end
240
-
241
- # The Lita logger.
242
- # @return [Lita::Logger] The Lita logger.
243
- # @since 3.2.0
244
- def log
245
- Lita.logger
246
- end
247
-
248
- # @see .translate
249
- def translate(*args)
250
- self.class.translate(*args)
251
- end
252
-
253
- alias_method :t, :translate
254
-
255
- private
256
-
257
- # Default options for new Faraday connections. Sets the user agent to the
258
- # current version of Lita.
259
- def default_faraday_options
260
- { headers: { "User-Agent" => "Lita v#{VERSION}" } }
261
- end
262
-
263
- # The handler's namespace for Redis.
264
- def redis_namespace
265
- "handlers:#{self.class.namespace}"
266
- end
5
+ extend ChatRouter
6
+ extend HTTPRouter
7
+ extend EventRouter
267
8
  end
268
9
  end
@@ -0,0 +1,130 @@
1
+ module Lita
2
+ class Handler
3
+ # A handler mixin that provides the methods necessary for responding to chat messages.
4
+ # @since 4.0.0
5
+ module ChatRouter
6
+ # Includes common handler methods in any class that includes {ChatRouter}.
7
+ def self.extended(klass)
8
+ klass.send(:include, Common)
9
+ end
10
+
11
+ # A Struct representing a chat route defined by a handler.
12
+ class Route < Struct.new(
13
+ :pattern,
14
+ :callback,
15
+ :command,
16
+ :required_groups,
17
+ :help,
18
+ :extensions
19
+ )
20
+ alias_method :command?, :command
21
+ end
22
+
23
+ # @overload route(pattern, method_name, **options)
24
+ # Creates a chat route.
25
+ # @param pattern [Regexp] A regular expression to match incoming messages against.
26
+ # @param method_name [Symbol, String] The name of the instance method to trigger.
27
+ # @param command [Boolean] Whether or not the message must be directed at the robot.
28
+ # @param restrict_to [Array<Symbol, String>, nil] An optional list of authorization
29
+ # groups the user must be in to trigger the route.
30
+ # @param help [Hash] An optional map of example invocations to descriptions.
31
+ # @param options [Hash] Aribtrary additional data that can be used by Lita extensions.
32
+ # @return [void]
33
+ # @overload route(pattern, **options)
34
+ # Creates a chat route.
35
+ # @param pattern [Regexp] A regular expression to match incoming messages against.
36
+ # @param command [Boolean] Whether or not the message must be directed at the robot.
37
+ # @param restrict_to [Array<Symbol, String>, nil] An optional list of authorization
38
+ # groups the user must be in to trigger the route.
39
+ # @param help [Hash] An optional map of example invocations to descriptions.
40
+ # @param options [Hash] Aribtrary additional data that can be used by Lita extensions.
41
+ # @yield The body of the route's callback.
42
+ # @return [void]
43
+ # @since 4.0.0
44
+ def route(pattern, method_name = nil, **options)
45
+ options = default_route_options.merge(options)
46
+ options[:restrict_to] = options[:restrict_to].nil? ? nil : Array(options[:restrict_to])
47
+ routes << Route.new(
48
+ pattern,
49
+ Callback.new(method_name || (proc if block_given?)),
50
+ options.delete(:command),
51
+ options.delete(:restrict_to),
52
+ options.delete(:help),
53
+ options
54
+ )
55
+ end
56
+
57
+ # A list of chat routes defined by the handler.
58
+ # @return [Array<Lita::Handler::Route>]
59
+ def routes
60
+ @routes ||= []
61
+ end
62
+
63
+ # The main entry point for the handler at runtime. Checks if the message
64
+ # matches any of the routes and invokes the route's method if it does.
65
+ # Called by {Lita::Robot#receive}.
66
+ # @param robot [Lita::Robot] The currently running robot.
67
+ # @param message [Lita::Message] The incoming message.
68
+ # @return [Boolean] Whether or not the message matched any routes.
69
+ def dispatch(robot, message)
70
+ routes.map do |route|
71
+ next unless route_applies?(route, message, robot)
72
+ log_dispatch(route)
73
+ dispatch_to_route(route, robot, message)
74
+ true
75
+ end.any?
76
+ end
77
+
78
+ # Dispatch directly to a {Route}, ignoring route conditions.
79
+ # @param route [Route] The route to invoke.
80
+ # @param robot [Lita::Robot] The currently running robot.
81
+ # @param message [Lita::Message] The incoming message.
82
+ # @return [void]
83
+ # @since 3.3.0
84
+ def dispatch_to_route(route, robot, message)
85
+ response = Response.new(message, route.pattern)
86
+ robot.hooks[:trigger_route].each { |hook| hook.call(response: response, route: route) }
87
+ handler = new(robot)
88
+ route.callback.call(handler, response)
89
+ rescue Exception => e
90
+ log_dispatch_error(e)
91
+ raise e if Lita.test_mode?
92
+ end
93
+
94
+ private
95
+
96
+ # The default options for every chat route.
97
+ def default_route_options
98
+ {
99
+ command: false,
100
+ restrict_to: nil,
101
+ help: {}
102
+ }
103
+ end
104
+
105
+ # Determines whether or not an incoming messages should trigger a route.
106
+ def route_applies?(route, message, robot)
107
+ RouteValidator.new(self, route, message, robot).call
108
+ end
109
+
110
+ # Logs the dispatch of message.
111
+ def log_dispatch(route)
112
+ Lita.logger.debug I18n.t(
113
+ "lita.handler.dispatch",
114
+ handler: name,
115
+ method: route.callback.method_name || "(block)"
116
+ )
117
+ end
118
+
119
+ # Logs an error encountered during dispatch.
120
+ def log_dispatch_error(e)
121
+ Lita.logger.error I18n.t(
122
+ "lita.handler.exception",
123
+ handler: name,
124
+ message: e.message,
125
+ backtrace: e.backtrace.join("\n")
126
+ )
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,114 @@
1
+ module Lita
2
+ class Handler
3
+ # Methods included in any class that includes at least one type of router.
4
+ # @since 4.0.0
5
+ module Common
6
+ # Adds common functionality to the class and initializes the handler's configuration.
7
+ def self.included(klass)
8
+ klass.extend(ClassMethods)
9
+ klass.extend(Namespace)
10
+ klass.extend(Configurable)
11
+ klass.configuration = Configuration.new
12
+ end
13
+
14
+ # Common class-level methods for all handlers.
15
+ module ClassMethods
16
+ # Returns the translation for a key, automatically namespaced to the handler.
17
+ # @param key [String] The key of the translation.
18
+ # @param hash [Hash] An optional hash of values to be interpolated in the string.
19
+ # @return [String] The translated string.
20
+ # @since 3.0.0
21
+ def translate(key, hash = {})
22
+ I18n.translate("lita.handlers.#{namespace}.#{key}", hash)
23
+ end
24
+
25
+ alias_method :t, :translate
26
+ end
27
+
28
+ # A Redis::Namespace scoped to the handler.
29
+ # @return [Redis::Namespace]
30
+ attr_reader :redis
31
+
32
+ # The running {Lita::Robot} instance.
33
+ # @return [Lita::Robot]
34
+ attr_reader :robot
35
+
36
+ # @param robot [Lita::Robot] The currently running robot.
37
+ def initialize(robot)
38
+ @robot = robot
39
+ @redis = Redis::Namespace.new(redis_namespace, redis: Lita.redis)
40
+ end
41
+
42
+ # Invokes the given block after the given number of seconds.
43
+ # @param interval [Integer] The number of seconds to wait before invoking the block.
44
+ # @yieldparam timer [Lita::Timer] The current {Lita::Timer} instance.
45
+ # @return [void]
46
+ # @since 3.0.0
47
+ def after(interval, &block)
48
+ Thread.new { Timer.new(interval: interval, &block).start }
49
+ end
50
+
51
+ # The handler's config object.
52
+ # @return [Object, Lita::Config] The handler's configuration object.
53
+ # @since 3.2.0
54
+ def config
55
+ if robot.config.handlers.respond_to?(self.class.namespace)
56
+ robot.config.handlers.public_send(self.class.namespace)
57
+ end
58
+ end
59
+
60
+ # Invokes the given block repeatedly, waiting the given number of seconds between each
61
+ # invocation.
62
+ # @param interval [Integer] The number of seconds to wait before each invocation of the block.
63
+ # @yieldparam timer [Lita::Timer] The current {Lita::Timer} instance.
64
+ # @return [void]
65
+ # @note The block should call {Lita::Timer#stop} at a terminating condition to avoid infinite
66
+ # recursion.
67
+ # @since 3.0.0
68
+ def every(interval, &block)
69
+ Thread.new { Timer.new(interval: interval, recurring: true, &block).start }
70
+ end
71
+
72
+ # Creates a new +Faraday::Connection+ for making HTTP requests.
73
+ # @param options [Hash] A set of options passed on to Faraday.
74
+ # @yield [builder] A Faraday builder object for adding middleware.
75
+ # @return [Faraday::Connection] The new connection object.
76
+ def http(options = {})
77
+ options = default_faraday_options.merge(options)
78
+
79
+ if block_given?
80
+ Faraday::Connection.new(nil, options, &proc)
81
+ else
82
+ Faraday::Connection.new(nil, options)
83
+ end
84
+ end
85
+
86
+ # The Lita logger.
87
+ # @return [Lita::Logger] The Lita logger.
88
+ # @since 3.2.0
89
+ def log
90
+ Lita.logger
91
+ end
92
+
93
+ # @see .translate
94
+ def translate(*args)
95
+ self.class.translate(*args)
96
+ end
97
+
98
+ alias_method :t, :translate
99
+
100
+ private
101
+
102
+ # Default options for new Faraday connections. Sets the user agent to the
103
+ # current version of Lita.
104
+ def default_faraday_options
105
+ { headers: { "User-Agent" => "Lita v#{VERSION}" } }
106
+ end
107
+
108
+ # The handler's namespace for Redis.
109
+ def redis_namespace
110
+ "handlers:#{self.class.namespace}"
111
+ end
112
+ end
113
+ end
114
+ end