lita 2.7.2 → 3.0.0

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