lita 3.3.1 → 4.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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