lita 0.0.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b75112b772520840f9fa41dd68bd92950b4e6714
4
- data.tar.gz: 567d841b587ec14f27a958a27081c384ca0a2c3f
3
+ metadata.gz: 685f6216c8c89f5312c06bf1ae319148b664f7f7
4
+ data.tar.gz: d56a154473e9e27b2bd670b8e523c436659775b8
5
5
  SHA512:
6
- metadata.gz: 5fc9e89ef72af2fd35c8b859baa2c67fec936cfc4faf899bf413ae4f5bf2abda4b2bcb1087b83228f7fdd73008743b2f3e5f99fddfc8a81b709a9e21ebbb8946
7
- data.tar.gz: 0ec04ae916a4adfdce35b6f9761a7a8634bdf3e511921ba8486e82081c73fb4da0f6848bb0accaa94d3cd1eee443bb2618c5d9976dedfc84d533caa221cf9f3e
6
+ metadata.gz: 73a167653dde936d3813aea9bcc9a8dc6d6c8de116773277a7e18b3e3e6a9a79f37854075f02d53c87d90c4f732e0541d4912557af591c5a6a7401b559ca2bf1
7
+ data.tar.gz: 0ae661969514a05918bcc7d11b8bef8c95cce9f4b40010aef44185d52158d39cf7774db522c47840ebbaae00c30c3a1550f1a8fff1cfad36f33b0bac65c1aecc
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 0.0.1 (June 15, 2013)
4
+
5
+ Initial release.
data/README.md CHANGED
@@ -8,6 +8,16 @@
8
8
 
9
9
  Automate your business and have fun with your very own robot companion.
10
10
 
11
+ ## Why?
12
+
13
+ Lita draws much inspiration from GitHub's fantastic [Hubot](http://hubot.github.com/), but has a few key differences and strengths:
14
+
15
+ * It's written in Ruby.
16
+ * It exposes the full power of Redis rather than using it to serialize JSON.
17
+ * Is easy to develop and test plugins for with the provied [RSpec](https://github.com/rspec/rspec) extras. Lita strongly encourages thorough testing of plugins.
18
+ * It uses uses the Ruby ecosystem's standard tools (RubyGems and Bundler) for plugins.
19
+ * It's thoroughly documented.
20
+
11
21
  ## Dependencies
12
22
 
13
23
  * Ruby 2.0
@@ -33,7 +43,7 @@ gem "lita-hipchat"
33
43
 
34
44
  Adapters will likely require some configuration to be able to connect. See the documentation for the adapter for details.
35
45
 
36
- Without installing an adapter, you can use the default shell adapter to chat with Lita in your terminal. Lita doesn't respond to any messages by default, however, so you'll want to add some new behavior to Lita via handlers.
46
+ Without installing an adapter, you can use the default shell adapter to chat with Lita in your terminal. Lita doesn't respond to many messages by default, however, so you'll want to add some new behavior to Lita via handlers.
37
47
 
38
48
  ## Handlers
39
49
 
@@ -54,23 +64,182 @@ Lita.configure do |config|
54
64
  config.adapter.username = "bottington"
55
65
  config.adapter.password = "secret"
56
66
  config.redis.host = "redis.example.com"
57
- config.handlers.karma.rate_limit = 300
67
+ config.handlers.karma.cooldown = 300
58
68
  config.handlers.google_images.safe_search = false
59
69
  end
60
70
  ```
61
71
 
62
72
  The main config objects are:
63
73
 
64
- * robot: General settings for Lita.
65
- * name: The name the bot will use on the chat service.
66
- * adapter: A symbol or string indicating the adapter to load.
67
- * adapter: Options for the chosen adapter. See the adapter's documentation.
68
- * redis: Options for the Redis connection. See the [Redis gem](https://github.com/redis/redis-rb) documentation.
69
- * handlers: Handlers may choose to expose a config object here with their own options. See the handler's documentation.
74
+ * `robot` - General settings for Lita.
75
+ * `name` - The name the bot will use on the chat service.
76
+ * `adapter` - A symbol or string indicating the adapter to load.
77
+ * `log_level` - A symbol or string indicating the severity level of log messages to output. Valid options are, in order of severity - `:debug`, `:info`, `:warn`, `:error`, and `:fatal`. For whichever level you choose, log messages of that severity and greater will be output. The default level is `:info`.
78
+ * `admins` - An array of string user IDs which tell Lita which users are considered administrators. Only these users will have access to Lita's `auth` command.
79
+ * `redis` - Options for the Redis connection. See the [Redis gem](https://github.com/redis/redis-rb) documentation.
80
+ * `adapter` - Options for the chosen adapter. See the adapter's documentation.
81
+ * `handlers` - Handlers may choose to expose a config object here with their own options. See the handler's documentation.
82
+
83
+ If you want to use a config file with a different name or location, invoke `lita` with the `-c` option and provide the path to the config file.
84
+
85
+ ## Authorization
86
+
87
+ Access to commands can be allowed for only certain users by means of authorization groups. Users set as admins (by adding their user IDs to the `config.robot.admins` array in Lita's configuration) have access to two commands:
88
+
89
+ ```
90
+ Lita: auth add joe committers
91
+ Lita: auth remove joe committers
92
+ ```
93
+
94
+ The first command adds a user whose ID or name is "joe" to the authorization group "committers." If the group doesn't yet exist, it is created. The second command removes joe from the group. Handlers can specify that a route (a method that matches an incoming message) requires that the user sending the message be in a certain authorization group. See the section on writing handlers for more details.
95
+
96
+ ## Online help
97
+
98
+ Message Lita `help` for a list of commands it knows about. You can also message it `help FOO` to list only commands beginning with FOO.
99
+
100
+ ## Writing an adapter
101
+
102
+ An adapter is a packaged as a RubyGem. The adapter is a class that inherits from `Lita::Adapter`, implements a few required methods, and is registered by calling `Lita.register_adapter(:symbol_that_identifies_the_adapter, TheAdapterClass)`.
103
+
104
+ ### Example
105
+
106
+ Here is a bare bones example of an adapter for the fictious chat service, FancyChat.
107
+
108
+ ``` ruby
109
+ module Lita
110
+ module Adapters
111
+ class FancyChat < Adapter
112
+ # Optional. Makes the bot produce an error message and quit upon start up
113
+ # if `config.adapter.username` or `config.adapter.password` are not set.
114
+ require_configs :username, :password
115
+
116
+ # Connects to the chat service and dispatches incoming messages to a
117
+ # Lita::Robot instance.
118
+ def run
119
+ end
120
+
121
+ # Sends a message from the robot to a user or room on the chat service.
122
+ def send_messages(target, strings)
123
+ end
124
+
125
+ # Sets the topic for a chat room.
126
+ def set_topic(target, topic)
127
+ end
128
+
129
+ # Does any clean up necessary when disconnecting from the chat service.
130
+ def shut_down
131
+ end
132
+ end
133
+
134
+ Lita.register_adapter(:fancy_chat, FancyChat)
135
+ end
136
+ end
137
+ ```
138
+
139
+ It's important to note that each adapter should employ its own thread or event mechanism so that incoming messages can still be processed even while a handler is processing a previous message.
140
+
141
+ For more detailed examples, check out the built in shell adapter or [lita-hipchat](https://github.com/jimmycuadra/lita-hipchat). Also check out the API documentation.
142
+
143
+ ## Writing a handler
144
+
145
+ A handler is packaged as a RubyGem. A handler is a class that inherits from `Lita::Handler` and is registered by calling `Lita.register_handler(TheHandlerClass)`. There are two components to a handler: route definitions, and the methods that implement those routes.
146
+
147
+ To define a route, use the class method `route`:
148
+
149
+ ``` ruby
150
+ route /^echo\s+(.+)/, to: :echo
151
+ ```
152
+
153
+ `route` takes a regular expression that will be used to determine whether or not an incoming message should trigger the route. The keyword argument `:to` is supplied the name of the method that should be called when this route is triggered. `route` takes two additional options:
154
+
155
+ * `:command` - A boolean which, if set to true, means that the route will only trigger when "directed" at the robot. Directed means that it's sent via a private message, or the message is prefixed with the bot's name in some form (optionally prefixed with an @, and optionally followed by a colon or comma and white space). This prefix is stripped from the message body itself, but the `command?` method available to handlers can be used if you need to determine whether or not a message was a command after it's been routed.
156
+ * `:required_groups` - A string, symbol, or array of strings/symbols, specifying authorization groups necessary to trigger the route. The user sending the message must be a member of at least one of the supplied groups. See the section on authorization for more information.
157
+
158
+ Here is an example of a route declaration with all the options:
159
+
160
+ ``` ruby
161
+ route /^echo\s+(.+)/, to :echo, command: true, required_groups: [:testers, :committers]
162
+ ```
163
+
164
+ Each method that is called by a route takes one argument, an array of matches extracted by calling `message.scan(route_pattern)`. Handler methods have several other methods available to them to assist in performing other tasks and in most cases responding to the user who sent the message:
165
+
166
+ * `reply` - Sends one or more string messages back to the source of the original message, either a private message or a chat room.
167
+ * `redis` - A `Redis::Namespace` object which provides each handler with its own isolated Redis store, suitable for many data persistence and manipulation tasks.
168
+ * `args` - The user's message as an array of strings, as it would be parsed by `Shellwords.split`. For example, if the message was "Lita: auth add joe committers", calling `args` would return `["auth", "add", "joe", "committers"]`. This is very handy for commands that take arguments in a way similar to how a UNIX shell would work.
169
+ * `user` - A `Lita::User` object for the user who sent the message.
170
+ * `command?` - A boolean indicating whether or not the current message was directed at the robot.
171
+ * `message_body` - The full body of the user's message, as a string.
172
+
173
+ To add entries to Lita's built-in `help` command for your handler's commands, add a class method called `help` that returns a hash, where the keys are the format of the command, and the values are a description of what it does.
174
+
175
+ ### Example
176
+
177
+ Here is a basic handler which simply echoes back whatever the user says.
178
+
179
+ ``` ruby
180
+ module Lita
181
+ module Handlers
182
+ class Echo < Handler
183
+ def self.help
184
+ { "#{Lita.config.robot.name}: echo FOO" => "Echoes back FOO." }
185
+ end
186
+
187
+ route /^echo\s+(.+)/, to: :echo
188
+
189
+ def echo(matches)
190
+ reply matches
191
+ end
192
+ end
193
+
194
+ Lita.register_handler(Echo)
195
+ end
196
+ end
197
+ ```
198
+ For more detailed examples, check out the built in authorization and help handlers or [lita-karma](https://github.com/jimmycuadra/lita-karma). Also check out the API documentation.
199
+
200
+ ## Testing
201
+
202
+ It's a core philosophy of Lita that any handlers you write for your robot should be as thoroughly tested as any other program you would write. To make this easier, Lita ships with some handy extras for [RSpec](https://github.com/rspec/rspec) that make testing a handler dead simple.
203
+
204
+ To include Lita's RSpec extras, require "lita/rspec" and add `lita: true` to any `describe` block where you want the extras:
205
+
206
+ ``` ruby
207
+ require "lita/rspec"
208
+
209
+ describe Lita::Handlers::MyHandler, lita: true do
210
+ # ...
211
+ end
212
+ ```
213
+
214
+ `Lita::RSpec` makes the following changes to make testing easier:
215
+
216
+ * `Lita.handlers` will return an array with only the class you're testing (`described_class`).
217
+ * All Redis interaction will be namespaced to a test environment and automatically cleared out before each example.
218
+ * `Lita::Robot#send_messages` will be stubbed out so the shell adapter doesn't actually spit messages out into your terminal.
219
+ * You have access to the following cached objects set with `let`: `robot`, `source`, and `user`.
220
+
221
+ The custom helper methods are where `Lita::RSpec` really shines. You can test routes very easily using this syntax:
222
+
223
+ ``` ruby
224
+ it { routes("some message").to(:some_method) }
225
+ it { routes("#{robot.name} directed message").to(:some_command_method) }
226
+ it { doesnt_route("message").to(:some_command_method) }
227
+ ```
228
+
229
+ You can also use the alias `does_not_route` if you prefer that to `doesnt_route`. These methods allow you to test your routing in a terse, expressive way.
230
+
231
+ To test the functionality of your methods, two additional methods are provided:
232
+
233
+ ``` ruby
234
+ expect_reply("Hello, #{user.name}.")
235
+ send_test_message("#{robot.name}: hi")
236
+ ```
237
+
238
+ `expect_reply` takes one or more argument matchers that it expects your handler to reply with. The arguments can be strings, regular expressions, or any other RSpec argument matcher. You can also use the alias `expect_replies` if you're passing multiple arguments.
70
239
 
71
- ## Acknowledgements
240
+ `send_test_message` does what you would expect: It sends the given message to your handler, using the `user` and `source` provided by the cached `let` objects.
72
241
 
73
- Lita draws much inspiration from GitHub's fantastic [Hubot](http://hubot.github.com/).
242
+ For negative message expectations, you can use `expect_no_reply` and its alias `expect_no_replies`, which also take any number of argument matchers.
74
243
 
75
244
  ## License
76
245
 
data/lib/lita.rb CHANGED
@@ -1,9 +1,13 @@
1
1
  require "forwardable"
2
+ require "logger"
2
3
  require "set"
3
4
  require "shellwords"
4
5
 
5
6
  require "redis-namespace"
6
7
 
8
+ require "lita/version"
9
+ require "lita/config"
10
+
7
11
  module Lita
8
12
  REDIS_NAMESPACE = "lita"
9
13
 
@@ -32,6 +36,17 @@ module Lita
32
36
  yield config
33
37
  end
34
38
 
39
+ def logger
40
+ @logger ||= begin
41
+ logger = Logger.new(STDERR)
42
+ logger.level = log_level
43
+ logger.formatter = proc do |severity, datetime, progname, msg|
44
+ "[#{datetime.utc}] #{severity}: #{msg}\n"
45
+ end
46
+ logger
47
+ end
48
+ end
49
+
35
50
  def redis
36
51
  @redis ||= begin
37
52
  redis = Redis.new(config.redis)
@@ -39,19 +54,36 @@ module Lita
39
54
  end
40
55
  end
41
56
 
42
- def run
43
- Config.load_user_config
57
+ def run(config_path = nil)
58
+ Config.load_user_config(config_path)
44
59
  Robot.new.run
45
60
  end
61
+
62
+ private
63
+
64
+ def log_level
65
+ level = config.robot.log_level
66
+
67
+ if level
68
+ begin
69
+ Logger.const_get(level.to_s.upcase)
70
+ rescue NameError
71
+ return Logger::INFO
72
+ end
73
+ else
74
+ Logger::INFO
75
+ end
76
+ end
46
77
  end
47
78
  end
48
79
 
49
- require "lita/version"
50
- require "lita/errors"
51
- require "lita/config"
80
+ require "lita/user"
52
81
  require "lita/source"
82
+ require "lita/authorization"
53
83
  require "lita/message"
54
84
  require "lita/robot"
55
85
  require "lita/adapter"
56
86
  require "lita/adapters/shell"
57
87
  require "lita/handler"
88
+ require "lita/handlers/authorization"
89
+ require "lita/handlers/help"
data/lib/lita/adapter.rb CHANGED
@@ -18,6 +18,12 @@ module Lita
18
18
  ensure_required_configs
19
19
  end
20
20
 
21
+ [:run, :send_messages, :set_topic, :shut_down].each do |method|
22
+ define_method(method) do |*args|
23
+ Lita.logger.warn("This adapter has not implemented ##{method}.")
24
+ end
25
+ end
26
+
21
27
  private
22
28
 
23
29
  def ensure_required_configs
@@ -31,9 +37,10 @@ module Lita
31
37
  end
32
38
 
33
39
  unless missing_keys.empty?
34
- raise Lita::ConfigError.new(
40
+ Lita.logger.fatal(
35
41
  "The following keys are required on config.adapter: #{missing_keys.join(", ")}"
36
42
  )
43
+ abort
37
44
  end
38
45
  end
39
46
  end
@@ -2,19 +2,26 @@ module Lita
2
2
  module Adapters
3
3
  class Shell < Adapter
4
4
  def run
5
+ user = User.new(1, name: "Shell User")
6
+ source = Source.new(user)
5
7
  puts 'Type "exit" or "quit" to end the session.'
8
+
6
9
  loop do
7
10
  print "#{robot.name} > "
8
- input = gets.chomp.strip
11
+ input = $stdin.gets.chomp.strip
9
12
  break if input == "exit" || input == "quit"
10
- source = Source.new("Shell User")
11
13
  message = Message.new(robot, input, source)
12
- robot.receive(message)
14
+ Thread.new { robot.receive(message) }
13
15
  end
14
16
  end
15
17
 
16
- def send_messages(target, *strings)
17
- puts *strings
18
+ def send_messages(target, strings)
19
+ puts
20
+ puts strings
21
+ end
22
+
23
+ def shut_down
24
+ puts
18
25
  end
19
26
  end
20
27
 
@@ -0,0 +1,29 @@
1
+ module Lita
2
+ module Authorization
3
+ class << self
4
+ def add_user_to_group(user, group)
5
+ return unless user_is_admin?(user)
6
+ redis.sadd(group, user.id)
7
+ end
8
+
9
+ def remove_user_from_group(user, group)
10
+ return unless user_is_admin?(user)
11
+ redis.srem(group, user.id)
12
+ end
13
+
14
+ def user_in_group?(user, group)
15
+ redis.sismember(group, user.id)
16
+ end
17
+
18
+ def user_is_admin?(user)
19
+ Array(Lita.config.robot.admins).include?(user.id)
20
+ end
21
+
22
+ private
23
+
24
+ def redis
25
+ @redis ||= Redis::Namespace.new("auth", redis: Lita.redis)
26
+ end
27
+ end
28
+ end
29
+ end
data/lib/lita/cli.rb CHANGED
@@ -10,10 +10,16 @@ module Lita
10
10
 
11
11
  default_task :start
12
12
 
13
+ class_option :config,
14
+ aliases: "-c",
15
+ banner: "PATH",
16
+ default: "lita_config.rb",
17
+ desc: "Path to the configuration file to use"
18
+
13
19
  desc "start", "Starts Lita"
14
20
  def start
15
21
  Bundler.require
16
- Lita.run
22
+ Lita.run(options[:config])
17
23
  end
18
24
 
19
25
  desc "new NAME", "Generates a new Lita project (default name: lita)"
data/lib/lita/config.rb CHANGED
@@ -5,19 +5,26 @@ module Lita
5
5
  c.robot = new
6
6
  c.robot.name = "Lita"
7
7
  c.robot.adapter = :shell
8
+ c.robot.log_level = :info
9
+ c.robot.admins = nil
8
10
  c.redis = new
9
11
  c.adapter = new
10
12
  c.handlers = new
11
13
  end
12
14
  end
13
15
 
14
- def self.load_user_config
15
- config_path = File.expand_path("lita_config.rb", Dir.pwd)
16
+ def self.load_user_config(config_path = nil)
17
+ config_path = "lita_config.rb" unless config_path
16
18
 
17
19
  begin
18
20
  load(config_path)
19
- rescue Exception
20
- raise ConfigError
21
+ rescue Exception => e
22
+ Lita.logger.fatal <<-MSG
23
+ Lita configuration file could not be processed. The exception was:
24
+ #{e.message}
25
+ #{e.backtrace.join("\n")}
26
+ MSG
27
+ abort
21
28
  end if File.exist?(config_path)
22
29
  end
23
30