lita 2.7.2 → 3.0.0

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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.rubocop.yml +26 -0
  4. data/CONTRIBUTING.md +1 -1
  5. data/README.md +6 -470
  6. data/Rakefile +3 -3
  7. data/lib/lita.rb +27 -19
  8. data/lib/lita/adapter.rb +46 -5
  9. data/lib/lita/adapters/shell.rb +18 -13
  10. data/lib/lita/authorization.rb +1 -1
  11. data/lib/lita/cli.rb +37 -23
  12. data/lib/lita/common.rb +35 -0
  13. data/lib/lita/config.rb +33 -13
  14. data/lib/lita/daemon.rb +15 -12
  15. data/lib/lita/handler.rb +49 -9
  16. data/lib/lita/handlers/authorization.rb +47 -47
  17. data/lib/lita/handlers/help.rb +16 -17
  18. data/lib/lita/handlers/info.rb +38 -0
  19. data/lib/lita/handlers/room.rb +32 -0
  20. data/lib/lita/http_route.rb +30 -19
  21. data/lib/lita/message.rb +3 -6
  22. data/lib/lita/rack_app.rb +11 -89
  23. data/lib/lita/response.rb +5 -15
  24. data/lib/lita/robot.rb +26 -10
  25. data/lib/lita/rspec.rb +6 -8
  26. data/lib/lita/rspec/handler.rb +49 -121
  27. data/lib/lita/rspec/matchers/event_subscription_matcher.rb +67 -0
  28. data/lib/lita/rspec/matchers/http_route_matcher.rb +72 -0
  29. data/lib/lita/rspec/matchers/route_matcher.rb +69 -0
  30. data/lib/lita/source.rb +5 -18
  31. data/lib/lita/timer.rb +45 -0
  32. data/lib/lita/user.rb +51 -4
  33. data/lib/lita/util.rb +5 -5
  34. data/lib/lita/version.rb +1 -1
  35. data/lita.gemspec +6 -3
  36. data/spec/lita/adapter_spec.rb +10 -2
  37. data/spec/lita/adapters/shell_spec.rb +3 -3
  38. data/spec/lita/authorization_spec.rb +11 -11
  39. data/spec/lita/config_spec.rb +8 -0
  40. data/spec/lita/daemon_spec.rb +65 -0
  41. data/spec/lita/handler_spec.rb +50 -11
  42. data/spec/lita/handlers/authorization_spec.rb +1 -1
  43. data/spec/lita/handlers/info_spec.rb +31 -0
  44. data/spec/lita/handlers/room_spec.rb +20 -0
  45. data/spec/lita/logger_spec.rb +1 -1
  46. data/spec/lita/message_spec.rb +4 -4
  47. data/spec/lita/rack_app_spec.rb +92 -0
  48. data/spec/lita/response_spec.rb +17 -8
  49. data/spec/lita/robot_spec.rb +23 -14
  50. data/spec/lita/rspec_spec.rb +1 -1
  51. data/spec/lita/source_spec.rb +0 -16
  52. data/spec/lita/timer_spec.rb +30 -0
  53. data/spec/lita/user_spec.rb +66 -6
  54. data/spec/lita_spec.rb +37 -0
  55. data/spec/spec_helper.rb +11 -0
  56. data/templates/locales/en.yml +90 -0
  57. data/templates/plugin/Rakefile +1 -1
  58. data/templates/plugin/lib/lita/plugin_type/plugin.tt +4 -0
  59. data/templates/plugin/locales/en.yml.tt +4 -0
  60. metadata +77 -18
  61. data/lib/lita/handlers/web.rb +0 -25
  62. data/spec/lita/handlers/web_spec.rb +0 -19
data/Rakefile CHANGED
@@ -1,8 +1,8 @@
1
1
  require "bundler/gem_tasks"
2
- require "cane/rake_task"
3
2
  require "rspec/core/rake_task"
3
+ require "rubocop/rake_task"
4
4
 
5
- Cane::RakeTask.new
6
5
  RSpec::Core::RakeTask.new
6
+ Rubocop::RakeTask.new
7
7
 
8
- task default: [:spec, :cane]
8
+ task default: [:spec, :rubocop]
@@ -3,12 +3,15 @@ require "logger"
3
3
  require "rbconfig"
4
4
  require "set"
5
5
  require "shellwords"
6
+ require "thread"
6
7
 
8
+ require "http_router"
9
+ require "ice_nine"
7
10
  require "faraday"
8
11
  require "multi_json"
12
+ require "puma"
9
13
  require "rack"
10
14
  require "redis-namespace"
11
- require "thin"
12
15
 
13
16
  # The main namespace for Lita. Provides a global registry of adapters and
14
17
  # handlers, as well as global configuration, logger, and Redis store.
@@ -85,26 +88,31 @@ module Lita
85
88
  # @return [void]
86
89
  def run(config_path = nil)
87
90
  Config.load_user_config(config_path)
91
+ Lita.config.finalize
92
+ self.locale = Lita.config.robot.locale
88
93
  Robot.new.run
89
94
  end
90
95
  end
91
96
  end
92
97
 
93
- require "lita/version"
94
- require "lita/config"
95
- require "lita/util"
96
- require "lita/logger"
97
- require "lita/user"
98
- require "lita/source"
99
- require "lita/authorization"
100
- require "lita/message"
101
- require "lita/response"
102
- require "lita/http_route"
103
- require "lita/rack_app"
104
- require "lita/robot"
105
- require "lita/adapter"
106
- require "lita/adapters/shell"
107
- require "lita/handler"
108
- require "lita/handlers/authorization"
109
- require "lita/handlers/help"
110
- require "lita/handlers/web"
98
+ require_relative "lita/version"
99
+ require_relative "lita/common"
100
+ require_relative "lita/config"
101
+ require_relative "lita/util"
102
+ require_relative "lita/logger"
103
+ require_relative "lita/user"
104
+ require_relative "lita/source"
105
+ require_relative "lita/authorization"
106
+ require_relative "lita/message"
107
+ require_relative "lita/response"
108
+ require_relative "lita/http_route"
109
+ require_relative "lita/rack_app"
110
+ require_relative "lita/timer"
111
+ require_relative "lita/robot"
112
+ require_relative "lita/adapter"
113
+ require_relative "lita/adapters/shell"
114
+ require_relative "lita/handler"
115
+ require_relative "lita/handlers/authorization"
116
+ require_relative "lita/handlers/help"
117
+ require_relative "lita/handlers/info"
118
+ require_relative "lita/handlers/room"
@@ -10,6 +10,18 @@ module Lita
10
10
  # @return [Array]
11
11
  attr_reader :required_configs
12
12
 
13
+ # The namespace for the adapter, used for registry and for I18n. If the handler is an
14
+ # anonymous class, it must explicitly define +self.name+.
15
+ # @return [String] The adapter's namespace.
16
+ # @raise [RuntimeError] If +self.name+ is not defined.
17
+ def namespace
18
+ if name
19
+ Util.underscore(name.split("::").last)
20
+ else
21
+ raise I18n.t("lita.adapter.name_required")
22
+ end
23
+ end
24
+
13
25
  # Defines configuration keys that are requried for the adapter to boot.
14
26
  # @param keys [String, Symbol] The required keys.
15
27
  # @return [void]
@@ -19,6 +31,16 @@ module Lita
19
31
  end
20
32
 
21
33
  alias_method :require_configs, :require_config
34
+
35
+ # Returns the translation for a key, automatically namespaced to the adapter.
36
+ # @param key [String] The key of the translation.
37
+ # @param hash [Hash] An optional hash of values to be interpolated in the string.
38
+ # @return [String] The translated string.
39
+ def translate(key, hash = {})
40
+ I18n.translate("lita.adapters.#{namespace}.#{key}", hash)
41
+ end
42
+
43
+ alias_method :t, :translate
22
44
  end
23
45
 
24
46
  # @param robot [Lita::Robot] The currently running robot.
@@ -27,6 +49,20 @@ module Lita
27
49
  ensure_required_configs
28
50
  end
29
51
 
52
+ # @!method join
53
+ # Joins the room with the specified ID.
54
+ # @param room_id [String] The ID of the room.
55
+ # @return [void]
56
+ # @abstract This should be implemented by the adapter.
57
+ # @since 3.0.0
58
+
59
+ # @!method part
60
+ # Parts from the room with the specified ID.
61
+ # @param room_id [String] The ID of the room.
62
+ # @return [void]
63
+ # @abstract This should be implemented by the adapter.
64
+ # @since 3.0.0
65
+
30
66
  # @!method run
31
67
  # The main loop. Should connect to the chat service, listen for incoming
32
68
  # messages, create {Lita::Message} objects from them, and dispatch them to
@@ -52,12 +88,19 @@ module Lita
52
88
  # Performs any clean up necessary when disconnecting from the chat service.
53
89
  # @return [void]
54
90
  # @abstract This should be implemented by the adapter.
55
- [:run, :send_messages, :set_topic, :shut_down].each do |method|
91
+ [:join, :part, :run, :send_messages, :set_topic, :shut_down].each do |method|
56
92
  define_method(method) do |*args|
57
- Lita.logger.warn("This adapter has not implemented ##{method}.")
93
+ Lita.logger.warn(I18n.t("lita.adapter.method_not_implemented", method: method))
58
94
  end
59
95
  end
60
96
 
97
+ # @see .translate
98
+ def translate(*args)
99
+ self.class.translate(*args)
100
+ end
101
+
102
+ alias_method :t, :translate
103
+
61
104
  private
62
105
 
63
106
  # Logs a fatal message and aborts if a required config key is not set.
@@ -72,9 +115,7 @@ module Lita
72
115
  end
73
116
 
74
117
  unless missing_keys.empty?
75
- Lita.logger.fatal(
76
- "The following keys are required on config.adapter: #{missing_keys.join(", ")}"
77
- )
118
+ Lita.logger.fatal(I18n.t("lita.adapter.missing_configs", configs: missing_keys.join(", ")))
78
119
  abort
79
120
  end
80
121
  end
@@ -1,4 +1,5 @@
1
1
  module Lita
2
+ # A namespace to hold all subclasses of {Adapter}.
2
3
  module Adapters
3
4
  # An adapter that runs Lita in a UNIX shell.
4
5
  class Shell < Adapter
@@ -7,21 +8,11 @@ module Lita
7
8
  # @return [void]
8
9
  def run
9
10
  user = User.create(1, name: "Shell User")
10
- source = Source.new(user: user)
11
- puts 'Type "exit" or "quit" to end the session.'
11
+ @source = Source.new(user: user)
12
+ puts t("startup_message")
12
13
  robot.trigger(:connected)
13
14
 
14
- loop do
15
- print "#{robot.name} > "
16
- input = $stdin.gets
17
- if input.nil?
18
- puts
19
- break
20
- end
21
- input = input.chomp.strip
22
- break if input == "exit" || input == "quit"
23
- robot.receive(build_message(input, source))
24
- end
15
+ run_loop
25
16
  end
26
17
 
27
18
  # Outputs outgoing messages to the shell.
@@ -51,6 +42,20 @@ module Lita
51
42
  message.command! if Lita.config.adapter.private_chat
52
43
  message
53
44
  end
45
+
46
+ def run_loop
47
+ loop do
48
+ print "#{robot.name} > "
49
+ input = $stdin.gets
50
+ if input.nil?
51
+ puts
52
+ break
53
+ end
54
+ input = input.chomp.strip
55
+ break if input == "exit" || input == "quit"
56
+ robot.receive(build_message(input, @source))
57
+ end
58
+ end
54
59
  end
55
60
 
56
61
  Lita.register_adapter(:shell, Shell)
@@ -52,7 +52,7 @@ module Lita
52
52
  # Returns a hash of authorization group names and the users in them.
53
53
  # @return [Hash] A map of +Symbol+ group names to +Lita::User+ objects.
54
54
  def groups_with_users
55
- groups.inject({}) do |list, group|
55
+ groups.reduce({}) do |list, group|
56
56
  list[group] = redis.smembers(group).map do |user_id|
57
57
  User.find_by_id(user_id)
58
58
  end
@@ -1,15 +1,29 @@
1
1
  require "thor"
2
2
 
3
- require "lita/daemon"
4
- require "lita/version"
3
+ require_relative "common"
4
+ require_relative "daemon"
5
+ require_relative "version"
5
6
 
6
7
  module Lita
7
8
  # The command line interface for Lita.
8
9
  class CLI < Thor
9
10
  include Thor::Actions
10
11
 
12
+ # The root file path for the templates directory.
13
+ # @note This is a magic method required by Thor for file operations.
14
+ # @return [String] The path.
11
15
  def self.source_root
12
- File.expand_path("../../../templates", __FILE__)
16
+ Lita.template_root
17
+ end
18
+
19
+ # Returns the full destination file path for the given file, using the supplied +default_path+
20
+ # as the base if run as root, otherwise falling back to the user's home directory.
21
+ # @param file_name [String] The name of the file.
22
+ # @param default_path [String] The base of the file path to use when run as root.
23
+ # @return [String] The full file path.
24
+ def self.file_path_for(file_name, default_path)
25
+ base_path = Process.euid == 0 ? default_path : ENV["HOME"]
26
+ File.join(base_path, file_name)
13
27
  end
14
28
 
15
29
  default_task :start
@@ -28,29 +42,25 @@ module Lita
28
42
  option :log_file,
29
43
  aliases: "-l",
30
44
  banner: "PATH",
31
- default: Process.euid == 0 ?
32
- "/var/log/lita.log" : File.expand_path("lita.log", ENV["HOME"]),
45
+ default: file_path_for("lita.log", "/var/log"),
33
46
  desc: "Path where the log file should be written when daemonized"
34
47
  option :pid_file,
35
48
  aliases: "-p",
36
49
  banner: "PATH",
37
- default: Process.euid == 0 ?
38
- "/var/run/lita.pid" : File.expand_path("lita.pid", ENV["HOME"]),
50
+ default: file_path_for("lita.pid", "/var/run"),
39
51
  desc: "Path where the PID file should be written when daemonized"
40
52
  option :kill,
41
53
  aliases: "-k",
42
54
  default: false,
43
55
  desc: "Kill existing Lita processes when starting the daemon",
44
56
  type: :boolean
57
+ # Starts Lita.
58
+ # @return [void]
45
59
  def start
46
60
  begin
47
61
  Bundler.require
48
62
  rescue Bundler::GemfileNotFound
49
- no_gemfile_warning = <<-WARN.chomp
50
- The default command "start" must be run inside a Lita project. Try running \
51
- `lita new` to generate a new Lita project or `lita help` to see all commands.
52
- WARN
53
- say no_gemfile_warning, :red
63
+ say I18n.t("lita.cli.no_gemfile_warning"), :red
54
64
  abort
55
65
  end
56
66
 
@@ -66,21 +76,32 @@ WARN
66
76
  end
67
77
 
68
78
  desc "new NAME", "Generates a new Lita project (default name: lita)"
79
+ # Generates a new Lita project.
80
+ # @param name [String] The directory name for the new project.
81
+ # @return [void]
69
82
  def new(name = "lita")
70
83
  directory "robot", name
71
84
  end
72
85
 
73
86
  desc "adapter NAME", "Generates a new Lita adapter"
87
+ # Generates a new Lita adapter.
88
+ # @param name [String] The name for the new adapter.
89
+ # @return [void]
74
90
  def adapter(name)
75
91
  generate_templates(generate_config(name, "adapter"))
76
92
  end
77
93
 
78
94
  desc "handler NAME", "Generates a new Lita handler"
95
+ # Generates a new Lita handler.
96
+ # @param name [String] The name for the new handler.
97
+ # @return [void]
79
98
  def handler(name)
80
99
  generate_templates(generate_config(name, "handler"))
81
100
  end
82
101
 
83
102
  desc "version", "Outputs the current version of Lita"
103
+ # Outputs the current version of Lita.
104
+ # @return [void]
84
105
  def version
85
106
  puts VERSION
86
107
  end
@@ -139,11 +160,8 @@ WARN
139
160
  "#{target}/spec/lita/#{namespace}/#{name}_spec.rb",
140
161
  config
141
162
  )
142
- template(
143
- "plugin/spec/spec_helper.tt",
144
- "#{target}/spec/spec_helper.rb",
145
- config
146
- )
163
+ template("plugin/spec/spec_helper.tt", "#{target}/spec/spec_helper.rb", config)
164
+ template("plugin/locales/en.yml.tt", "#{target}/locales/en.yml", config)
147
165
  copy_file("plugin/Gemfile", "#{target}/Gemfile")
148
166
  template("plugin/gemspec.tt", "#{target}/#{gem_name}.gemspec", config)
149
167
  copy_file("plugin/gitignore", "#{target}/.gitignore")
@@ -161,13 +179,9 @@ WARN
161
179
  end
162
180
 
163
181
  def optional_content
164
- coveralls_question = <<-Q.chomp
165
- Do you want to generate code coverage information with SimpleCov \
166
- and Coveralls.io?
167
- Q
168
182
  {
169
- travis: yes?("Do you want to test your plugin on Travis CI?"),
170
- coveralls: yes?(coveralls_question)
183
+ travis: yes?(I18n.t("lita.cli.travis_question")),
184
+ coveralls: yes?(I18n.t("lita.cli.coveralls_question"))
171
185
  }
172
186
  end
173
187
  end
@@ -0,0 +1,35 @@
1
+ require "i18n"
2
+ require "i18n/backend/fallbacks"
3
+
4
+ module Lita
5
+ class << self
6
+ # Adds one or more paths to the I18n load path and reloads I18n.
7
+ # @param paths [String, Array<String>] The path(s) to add.
8
+ # @return [void]
9
+ # @since 3.0.0
10
+ def load_locales(paths)
11
+ I18n.load_path.concat(Array(paths))
12
+ I18n.reload!
13
+ end
14
+
15
+ # Sets I18n.locale, normalizing the provided locale name.
16
+ # @param new_locale [Symbol, String] The code of the locale to use.
17
+ # @return [void]
18
+ # @since 3.0.0
19
+ def locale=(new_locale)
20
+ I18n.locale = new_locale.to_s.tr("_", "-")
21
+ end
22
+
23
+ # The absolute path to Lita's templates directory.
24
+ # @return [String] The path.
25
+ # @since 3.0.0
26
+ def template_root
27
+ File.expand_path("../../../templates", __FILE__)
28
+ end
29
+ end
30
+ end
31
+
32
+ I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
33
+ Lita.load_locales(Dir[File.join(Lita.template_root, "locales", "*.yml")])
34
+ I18n.enforce_available_locales = false
35
+ Lita.locale = ENV["LANG"] unless ENV["LANG"].nil?
@@ -6,15 +6,9 @@ module Lita
6
6
  # @return [Lita::Config] The default configuration.
7
7
  def default_config
8
8
  new.tap do |c|
9
- c.robot = new
10
- c.robot.name = "Lita"
11
- c.robot.adapter = :shell
12
- c.robot.log_level = :info
13
- c.robot.admins = nil
9
+ load_robot_configs(c)
14
10
  c.redis = new
15
- c.http = new
16
- c.http.port = 8080
17
- c.http.debug = false
11
+ load_http_configs(c)
18
12
  c.adapter = new
19
13
  c.handlers = new
20
14
  load_handler_configs(c)
@@ -30,11 +24,11 @@ module Lita
30
24
  begin
31
25
  load(config_path)
32
26
  rescue Exception => e
33
- Lita.logger.fatal <<-MSG
34
- Lita configuration file could not be processed. The exception was:
35
- #{e.message}
36
- #{e.backtrace.join("\n")}
37
- MSG
27
+ Lita.logger.fatal I18n.t(
28
+ "lita.config.exception",
29
+ message: e.message,
30
+ backtrace: e.backtrace.join("\n")
31
+ )
38
32
  abort
39
33
  end if File.exist?(config_path)
40
34
  end
@@ -50,6 +44,25 @@ MSG
50
44
  handler.default_config(handler_config)
51
45
  end
52
46
  end
47
+
48
+ # Adds and populates a Config object for the built-in web server.
49
+ def load_http_configs(config)
50
+ config.http = new
51
+ config.http.host = "0.0.0.0"
52
+ config.http.port = 8080
53
+ config.http.min_threads = 0
54
+ config.http.max_threads = 16
55
+ end
56
+
57
+ # Adds and populates a Config object for the Robot.
58
+ def load_robot_configs(config)
59
+ config.robot = new
60
+ config.robot.name = "Lita"
61
+ config.robot.adapter = :shell
62
+ config.robot.locale = I18n.locale
63
+ config.robot.log_level = :info
64
+ config.robot.admins = nil
65
+ end
53
66
  end
54
67
 
55
68
  # Sets a config key.
@@ -67,6 +80,13 @@ MSG
67
80
  super(key.to_sym)
68
81
  end
69
82
 
83
+ # Deeply freezes the object to prevent any further mutation.
84
+ # @return [void]
85
+ # @since 3.0.0
86
+ def finalize
87
+ IceNine.deep_freeze!(self)
88
+ end
89
+
70
90
  # Allows keys to be read and written with struct-like syntax.
71
91
  def method_missing(name, *args)
72
92
  name_string = name.to_s