lita 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b75112b772520840f9fa41dd68bd92950b4e6714
4
+ data.tar.gz: 567d841b587ec14f27a958a27081c384ca0a2c3f
5
+ SHA512:
6
+ metadata.gz: 5fc9e89ef72af2fd35c8b859baa2c67fec936cfc4faf899bf413ae4f5bf2abda4b2bcb1087b83228f7fdd73008743b2f3e5f99fddfc8a81b709a9e21ebbb8946
7
+ data.tar.gz: 0ec04ae916a4adfdce35b6f9761a7a8634bdf3e511921ba8486e82081c73fb4da0f6848bb0accaa94d3cd1eee443bb2618c5d9976dedfc84d533caa221cf9f3e
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ script: bundle exec rspec
5
+ before_install:
6
+ - gem update --system
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # Lita
2
+
3
+ [![Build Status](https://travis-ci.org/jimmycuadra/lita.png)](https://travis-ci.org/jimmycuadra/lita)
4
+ [![Code Climate](https://codeclimate.com/github/jimmycuadra/lita.png)](https://codeclimate.com/github/jimmycuadra/lita)
5
+ [![Coverage Status](https://coveralls.io/repos/jimmycuadra/lita/badge.png)](https://coveralls.io/r/jimmycuadra/lita)
6
+
7
+ **Lita** is a chat bot written in Ruby with persistent storage provided by [Redis](http://redis.io/). It can connect to any chat service (given that there is an [adapter](#adapters) available for it) and can have new behavior added via [handlers](#handlers). The plugin system is managed with regular RubyGems and [Bundler](http://gembundler.com/).
8
+
9
+ Automate your business and have fun with your very own robot companion.
10
+
11
+ ## Dependencies
12
+
13
+ * Ruby 2.0
14
+ * Redis
15
+
16
+ ## Installation
17
+
18
+ First, install the gem with `gem install lita`. This gives you access to the `lita` commmand. Run `lita help` to list available tasks.
19
+
20
+ Generate a new Lita instance by running `lita new NAME`. This will create a new directory called NAME (defaults to "lita") with a Gemfile and Lita configuration file.
21
+
22
+ ## Usage
23
+
24
+ To start your Lita instance, simply run `bundle exec lita`. This will load up all the plugins (adapters and handlers) declared in your Gemfile, load any configuration you've defined (more on that later) and start the bot.
25
+
26
+ ## Adapters
27
+
28
+ The core Lita gem by itself doesn't do much. To make real use of it, you'll want to install an adapter gem to allow Lita to connect to the chat service of your choice. Find the gem for the service you want to use on [the list of adapters](https://github.com/jimmycuadra/lita/wiki/Adapters), then add it to your Gemfile. For example:
29
+
30
+ ``` ruby
31
+ gem "lita-hipchat"
32
+ ```
33
+
34
+ Adapters will likely require some configuration to be able to connect. See the documentation for the adapter for details.
35
+
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.
37
+
38
+ ## Handlers
39
+
40
+ Handlers are gems that add new behavior to Lita. They are responsible for listening for incoming messages and responding to them appropriately. Find the handler gems you want for your bot on [the list of handlers](https://github.com/jimmycuadra/lita/wiki/Handlers), then add them to your Gemfile. For example:
41
+
42
+ ``` ruby
43
+ gem "lita-karma"
44
+ ```
45
+
46
+ ## Configuration
47
+
48
+ To configure Lita, edit the file `lita_config.rb` generated by the `lita new` command. This is just a plain Ruby file that will be evaluated when the bot is starting up. A Lita config file looks something like this:
49
+
50
+ ``` ruby
51
+ Lita.configure do |config|
52
+ config.robot.name = "Sir Bottington"
53
+ config.robot.adapter = :example_chat_service
54
+ config.adapter.username = "bottington"
55
+ config.adapter.password = "secret"
56
+ config.redis.host = "redis.example.com"
57
+ config.handlers.karma.rate_limit = 300
58
+ config.handlers.google_images.safe_search = false
59
+ end
60
+ ```
61
+
62
+ The main config objects are:
63
+
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.
70
+
71
+ ## Acknowledgements
72
+
73
+ Lita draws much inspiration from GitHub's fantastic [Hubot](http://hubot.github.com/).
74
+
75
+ ## License
76
+
77
+ [MIT](http://opensource.org/licenses/MIT)
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/lita ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler"
4
+ require "lita/cli"
5
+
6
+ Lita::CLI.start
data/lib/lita.rb ADDED
@@ -0,0 +1,57 @@
1
+ require "forwardable"
2
+ require "set"
3
+ require "shellwords"
4
+
5
+ require "redis-namespace"
6
+
7
+ module Lita
8
+ REDIS_NAMESPACE = "lita"
9
+
10
+ class << self
11
+ def adapters
12
+ @adapters ||= {}
13
+ end
14
+
15
+ def register_adapter(key, adapter)
16
+ adapters[key.to_sym] = adapter
17
+ end
18
+
19
+ def handlers
20
+ @handlers ||= Set.new
21
+ end
22
+
23
+ def register_handler(handler)
24
+ handlers << handler
25
+ end
26
+
27
+ def config
28
+ @config ||= Config.default_config
29
+ end
30
+
31
+ def configure
32
+ yield config
33
+ end
34
+
35
+ def redis
36
+ @redis ||= begin
37
+ redis = Redis.new(config.redis)
38
+ Redis::Namespace.new(REDIS_NAMESPACE, redis: redis)
39
+ end
40
+ end
41
+
42
+ def run
43
+ Config.load_user_config
44
+ Robot.new.run
45
+ end
46
+ end
47
+ end
48
+
49
+ require "lita/version"
50
+ require "lita/errors"
51
+ require "lita/config"
52
+ require "lita/source"
53
+ require "lita/message"
54
+ require "lita/robot"
55
+ require "lita/adapter"
56
+ require "lita/adapters/shell"
57
+ require "lita/handler"
@@ -0,0 +1,40 @@
1
+ module Lita
2
+ class Adapter
3
+ attr_reader :robot
4
+
5
+ class << self
6
+ attr_reader :required_configs
7
+
8
+ def require_config(*keys)
9
+ @required_configs ||= []
10
+ @required_configs.concat(keys.flatten.map(&:to_sym))
11
+ end
12
+
13
+ alias_method :require_configs, :require_config
14
+ end
15
+
16
+ def initialize(robot)
17
+ @robot = robot
18
+ ensure_required_configs
19
+ end
20
+
21
+ private
22
+
23
+ def ensure_required_configs
24
+ required_configs = self.class.required_configs
25
+ return if required_configs.nil?
26
+
27
+ missing_keys = []
28
+
29
+ required_configs.each do |key|
30
+ missing_keys << key unless Lita.config.adapter[key]
31
+ end
32
+
33
+ unless missing_keys.empty?
34
+ raise Lita::ConfigError.new(
35
+ "The following keys are required on config.adapter: #{missing_keys.join(", ")}"
36
+ )
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,23 @@
1
+ module Lita
2
+ module Adapters
3
+ class Shell < Adapter
4
+ def run
5
+ puts 'Type "exit" or "quit" to end the session.'
6
+ loop do
7
+ print "#{robot.name} > "
8
+ input = gets.chomp.strip
9
+ break if input == "exit" || input == "quit"
10
+ source = Source.new("Shell User")
11
+ message = Message.new(robot, input, source)
12
+ robot.receive(message)
13
+ end
14
+ end
15
+
16
+ def send_messages(target, *strings)
17
+ puts *strings
18
+ end
19
+ end
20
+
21
+ Lita.register_adapter(:shell, Shell)
22
+ end
23
+ end
data/lib/lita/cli.rb ADDED
@@ -0,0 +1,24 @@
1
+ require "thor"
2
+
3
+ module Lita
4
+ class CLI < Thor
5
+ include Thor::Actions
6
+
7
+ def self.source_root
8
+ File.expand_path("../../..", __FILE__)
9
+ end
10
+
11
+ default_task :start
12
+
13
+ desc "start", "Starts Lita"
14
+ def start
15
+ Bundler.require
16
+ Lita.run
17
+ end
18
+
19
+ desc "new NAME", "Generates a new Lita project (default name: lita)"
20
+ def new(name = "lita")
21
+ directory "skeleton", name
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,41 @@
1
+ module Lita
2
+ class Config < Hash
3
+ def self.default_config
4
+ new.tap do |c|
5
+ c.robot = new
6
+ c.robot.name = "Lita"
7
+ c.robot.adapter = :shell
8
+ c.redis = new
9
+ c.adapter = new
10
+ c.handlers = new
11
+ end
12
+ end
13
+
14
+ def self.load_user_config
15
+ config_path = File.expand_path("lita_config.rb", Dir.pwd)
16
+
17
+ begin
18
+ load(config_path)
19
+ rescue Exception
20
+ raise ConfigError
21
+ end if File.exist?(config_path)
22
+ end
23
+
24
+ def []=(key, value)
25
+ super(key.to_sym, value)
26
+ end
27
+
28
+ def [](key)
29
+ super(key.to_sym)
30
+ end
31
+
32
+ def method_missing(name, *args)
33
+ name_string = name.to_s
34
+ if name_string.chomp!("=")
35
+ self[name_string] = args.first
36
+ else
37
+ self[name_string]
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,6 @@
1
+ module Lita
2
+ class Error < StandardError; end
3
+
4
+ class UnknownAdapterError < Error; end
5
+ class ConfigError < Error; end
6
+ end
@@ -0,0 +1,73 @@
1
+ module Lita
2
+ class Handler
3
+ extend Forwardable
4
+
5
+ attr_reader :redis
6
+ private :redis
7
+
8
+ def_delegators :@message, :args, :command?, :scan
9
+
10
+ class Route < Struct.new(:pattern, :method_name, :command)
11
+ alias_method :command?, :command
12
+ end
13
+
14
+ class << self
15
+ def route(pattern, to: nil, command: false)
16
+ @routes ||= []
17
+ @routes << Route.new(pattern, to, command)
18
+ end
19
+
20
+ def dispatch(robot, message)
21
+ instance = new(robot, message)
22
+
23
+ @routes.each do |route|
24
+ if route_applies?(route, instance)
25
+ instance.public_send(
26
+ route.method_name,
27
+ matches_for_route(route, instance)
28
+ )
29
+ end
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def route_applies?(route, instance)
36
+ if route.pattern === instance.message_body
37
+ if route.command?
38
+ return instance.command?
39
+ else
40
+ return true
41
+ end
42
+ end
43
+
44
+ false
45
+ end
46
+
47
+ def matches_for_route(route, instance)
48
+ instance.scan(route.pattern)
49
+ end
50
+ end
51
+
52
+ def initialize(robot, message)
53
+ @robot = robot
54
+ @message = message
55
+ @redis = Redis::Namespace.new(redis_namespace, redis: Lita.redis)
56
+ end
57
+
58
+ def reply(*strings)
59
+ @robot.send_messages(@message.source, *strings)
60
+ end
61
+
62
+ def message_body
63
+ @message.body
64
+ end
65
+
66
+ private
67
+
68
+ def redis_namespace
69
+ name = self.class.name.split("::").last.downcase
70
+ "handlers:#{name}"
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,37 @@
1
+ module Lita
2
+ class Message
3
+ extend Forwardable
4
+
5
+ attr_reader :body, :source
6
+ alias_method :message, :body
7
+
8
+ def_delegators :@body, :scan
9
+
10
+ def initialize(robot, body, source)
11
+ @robot = robot
12
+ @body = body
13
+ @source = source
14
+
15
+ @command = !!@body.sub!(/^\s*@?#{@robot.name}[:,]?\s*/, "")
16
+ end
17
+
18
+ def args
19
+ begin
20
+ command, *args = message.shellsplit
21
+ rescue ArgumentError
22
+ command, *args =
23
+ message.split(/\s+/).map(&:shellescape).join(" ").shellsplit
24
+ end
25
+
26
+ args
27
+ end
28
+
29
+ def command!
30
+ @command = true
31
+ end
32
+
33
+ def command?
34
+ @command
35
+ end
36
+ end
37
+ end
data/lib/lita/robot.rb ADDED
@@ -0,0 +1,36 @@
1
+ module Lita
2
+ class Robot
3
+ attr_reader :name
4
+
5
+ def initialize
6
+ @name = Lita.config.robot.name
7
+ load_adapter
8
+ end
9
+
10
+ def receive(message)
11
+ Lita.handlers.each { |handler| handler.dispatch(self, message) }
12
+ end
13
+
14
+ def run
15
+ @adapter.run
16
+ end
17
+
18
+ def send_messages(target, *strings)
19
+ @adapter.send_messages(target, strings.flatten)
20
+ end
21
+ alias_method :send_message, :send_messages
22
+
23
+ private
24
+
25
+ def load_adapter
26
+ adapter_name = Lita.config.robot.adapter
27
+ adapter_class = Lita.adapters[adapter_name.to_sym]
28
+
29
+ unless adapter_class
30
+ raise UnknownAdapterError.new("Unknown adapter: :#{adapter_name}")
31
+ end
32
+
33
+ @adapter = adapter_class.new(self)
34
+ end
35
+ end
36
+ end
data/lib/lita/rspec.rb ADDED
@@ -0,0 +1,51 @@
1
+ module Lita
2
+ module RSpec
3
+ def self.included(base)
4
+ base.class_eval do
5
+ let(:robot) { Robot.new }
6
+
7
+ before do
8
+ allow(Lita).to receive(:handlers).and_return([described_class])
9
+ stub_const("Lita::REDIS_NAMESPACE", "lita.test")
10
+ keys = Lita.redis.keys("*")
11
+ Lita.redis.del(keys) unless keys.empty?
12
+ end
13
+ end
14
+ end
15
+
16
+ def send_test_message(body)
17
+ source = Source.new("Test User")
18
+ message = Message.new(robot, body, source)
19
+ robot.receive(message)
20
+ end
21
+
22
+ def routes(message)
23
+ RouteMatcher.new(self, message)
24
+ end
25
+
26
+ def does_not_route(message)
27
+ RouteMatcher.new(self, message, invert: true)
28
+ end
29
+ alias_method :doesnt_route, :does_not_route
30
+ end
31
+
32
+ class RouteMatcher
33
+ def initialize(context, message_body, invert: false)
34
+ @context = context
35
+ @message_body = message_body
36
+ @method = invert ? :not_to : :to
37
+ end
38
+
39
+ def to(route)
40
+ @context.expect_any_instance_of(
41
+ @context.described_class
42
+ ).public_send(@method, @context.receive(route))
43
+
44
+ @context.send_test_message(@message_body)
45
+ end
46
+ end
47
+ end
48
+
49
+ RSpec.configure do |config|
50
+ config.include Lita::RSpec, lita_handler: true
51
+ end
@@ -0,0 +1,10 @@
1
+ module Lita
2
+ class Source
3
+ attr_reader :user, :room
4
+
5
+ def initialize(user, room = nil)
6
+ @user = user
7
+ @room = room
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ module Lita
2
+ VERSION = "0.0.1"
3
+ end
data/lita.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "lita/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "lita"
8
+ spec.version = Lita::VERSION
9
+ spec.authors = ["Jimmy Cuadra"]
10
+ spec.email = ["jimmy@jimmycuadra.com"]
11
+ spec.description = %q{A multi-service chat bot with extendable behavior.}
12
+ spec.summary = %q{A multi-service chat bot with extendable behavior.}
13
+ spec.homepage = "https://github.com/jimmycuadra/lita"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "bundler", "~> 1.3"
22
+ spec.add_runtime_dependency "redis-namespace", "~> 1.3.0"
23
+ spec.add_runtime_dependency "thor", "~> 0.18.1"
24
+
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "rspec", ">= 2.14.0rc1"
27
+ spec.add_development_dependency "simplecov"
28
+ spec.add_development_dependency "coveralls"
29
+ end
data/skeleton/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "lita"
4
+
5
+ # Uncomment to use the HipChat adapter
6
+ # gem "lita-hipchat"
7
+
8
+ # Uncomment to use the IRC adapter
9
+ # gem "lita-irc"
@@ -0,0 +1,20 @@
1
+ Lita.configure do |config|
2
+ # The name your robot will use.
3
+ config.robot.name = "Lita"
4
+
5
+ # The adapter you want to connect with. Make sure you've added the
6
+ # appropriate gem to the Gemfile.
7
+ config.robot.adapter = :shell
8
+
9
+ ## Example: Set options for the chosen adapter.
10
+ # config.adapter.username = "myname"
11
+ # config.adapter.password = "secret"
12
+
13
+ ## Example: Set options for the Redis connection.
14
+ # config.redis.host = "127.0.0.1"
15
+ # config.redis.port = 1234
16
+
17
+ ## Example: Set configuration for any loaded handlers. See the handler's
18
+ ## documentation for options.
19
+ # config.handlers.some_handler.some_config_key = "value"
20
+ end
@@ -0,0 +1,29 @@
1
+ require "spec_helper"
2
+
3
+ describe Lita::Adapter do
4
+ let(:robot) { double("Robot") }
5
+
6
+ subject { described_class.new(robot) }
7
+
8
+ it "stores a Robot" do
9
+ expect(subject.robot).to eql(robot)
10
+ end
11
+
12
+ describe ".require_config" do
13
+ let(:adapter_class) do
14
+ Class.new(described_class) do
15
+ require_config :foo
16
+ require_configs :bar, :baz
17
+ require_configs ["blah", :bleh]
18
+ end
19
+ end
20
+
21
+ subject { adapter_class.new(robot) }
22
+
23
+ it "ensures that config keys are present on initialization" do
24
+ expect do
25
+ subject
26
+ end.to raise_error(Lita::ConfigError, /foo, bar, baz, blah, bleh/)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,27 @@
1
+ describe Lita::Adapters::Shell do
2
+ let(:robot) do
3
+ robot = double("Robot")
4
+ allow(robot).to receive(:name).and_return("Lita")
5
+ robot
6
+ end
7
+
8
+ subject { described_class.new(robot) }
9
+
10
+ describe "#run" do
11
+ before { allow(subject).to receive(:puts) }
12
+
13
+ it "passes input to the Robot and breaks on an exit message" do
14
+ expect(subject).to receive(:print).with("#{robot.name} > ").twice
15
+ allow(subject).to receive(:gets).and_return("foo", "exit")
16
+ expect(robot).to receive(:receive).with(an_instance_of(Lita::Message))
17
+ subject.run
18
+ end
19
+ end
20
+
21
+ describe "#send_message" do
22
+ it "prints its input" do
23
+ expect(subject).to receive(:puts).with("bar")
24
+ subject.send_messages(double("target"), "bar")
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,47 @@
1
+ require "spec_helper"
2
+
3
+ describe Lita::Config do
4
+ let(:value) { double("arbitrary config key's value") }
5
+
6
+ it "allows hash-style access with symbols or strings" do
7
+ subject[:foo] = value
8
+ expect(subject[:foo]).to eql(value)
9
+ expect(subject["foo"]).to eql(value)
10
+ end
11
+
12
+ it "allows struct-style access" do
13
+ subject.foo = value
14
+ expect(subject.foo).to eql(value)
15
+ end
16
+
17
+ describe ".default_config" do
18
+ it "has predefined values for certain keys" do
19
+ expect(described_class.default_config.robot.name).to eq("Lita")
20
+ expect(described_class.default_config.robot.adapter).to eq(:shell)
21
+ end
22
+ end
23
+
24
+ describe ".load_user_config" do
25
+ it "loads and evals lita_config.rb" do
26
+ allow(File).to receive(:exist?).and_return(true)
27
+ allow(described_class).to receive(:load) do
28
+ Lita.configure { |config| config.robot.name = "Not Lita" }
29
+ end
30
+ described_class.load_user_config
31
+ expect(Lita.config.robot.name).to eq("Not Lita")
32
+ end
33
+
34
+ it "doesn't attempt to load lita_config.rb if it doesn't exist" do
35
+ expect(described_class).not_to receive(:load)
36
+ described_class.load_user_config
37
+ end
38
+
39
+ it "raises an exception if lita_config.rb raises an exception" do
40
+ allow(File).to receive(:exist?).and_return(true)
41
+ allow(described_class).to receive(:load) { Lita.non_existent_method }
42
+ expect do
43
+ described_class.load_user_config
44
+ end.to raise_error(Lita::ConfigError)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,110 @@
1
+ require "spec_helper"
2
+
3
+ describe Lita::Handler do
4
+ let(:robot) do
5
+ robot = double("Robot")
6
+ allow(robot).to receive(:name).and_return("Lita")
7
+ robot
8
+ end
9
+
10
+ let(:message) do
11
+ message = double("Message")
12
+ allow(message).to receive(:scan).and_return(matches)
13
+ allow(message).to receive(:command?).and_return(false)
14
+ allow(message).to receive(:source).and_return(source)
15
+ message
16
+ end
17
+
18
+ let(:matches) { double("MatchData") }
19
+
20
+ let(:source) { double("Source") }
21
+
22
+ let(:handler_class) do
23
+ Class.new(described_class) do
24
+ route(/\w{3}/, to: :foo)
25
+ route(/\w{4}/, to: :blah, command: true)
26
+
27
+ def foo(matches)
28
+ end
29
+
30
+ def blah(matches)
31
+ end
32
+
33
+ def self.name
34
+ "Lita::Handlers::Test"
35
+ end
36
+ end
37
+ end
38
+
39
+ subject { described_class.new(robot, message) }
40
+
41
+ describe ".dispatch" do
42
+ it "routes a matching message to the supplied method" do
43
+ allow(message).to receive(:body).and_return("bar")
44
+ expect_any_instance_of(handler_class).to receive(:foo)
45
+ handler_class.dispatch(robot, message)
46
+ end
47
+
48
+ it "routes a matching message even if addressed to the Robot" do
49
+ allow(message).to receive(:body).and_return("#{robot.name}: bar")
50
+ allow(message).to receive(:command?).and_return(true)
51
+ expect_any_instance_of(handler_class).to receive(:foo)
52
+ handler_class.dispatch(robot, message)
53
+ end
54
+
55
+ it "routes a command message to the supplied method" do
56
+ allow(message).to receive(:body).and_return("#{robot.name}: bar")
57
+ allow(message).to receive(:command?).and_return(true)
58
+ expect_any_instance_of(handler_class).to receive(:blah)
59
+ handler_class.dispatch(robot, message)
60
+ end
61
+
62
+ it "requires command routes to be addressed to the Robot" do
63
+ allow(message).to receive(:body).and_return("blah")
64
+ expect_any_instance_of(handler_class).not_to receive(:blah)
65
+ handler_class.dispatch(robot, message)
66
+ end
67
+
68
+ it "doesn't route messages that don't match anything" do
69
+ allow(message).to receive(:body).and_return("yo")
70
+ expect_any_instance_of(handler_class).not_to receive(:foo)
71
+ expect_any_instance_of(handler_class).not_to receive(:blah)
72
+ handler_class.dispatch(robot, message)
73
+ end
74
+ end
75
+
76
+ describe "#args" do
77
+ it "delegates to Message" do
78
+ expect(message).to receive(:args)
79
+ subject.args
80
+ end
81
+ end
82
+
83
+ describe "#command?" do
84
+ it "delegates to Message" do
85
+ expect(message).to receive(:command?)
86
+ subject.command?
87
+ end
88
+ end
89
+
90
+ describe "#message_body" do
91
+ it "delegates to Message" do
92
+ expect(message).to receive(:body)
93
+ subject.message_body
94
+ end
95
+ end
96
+
97
+ describe "#reply" do
98
+ it "calls Robot#send_message with the messages to send" do
99
+ expect(robot).to receive(:send_messages).with(source, "foo", "bar")
100
+ subject.reply("foo", "bar")
101
+ end
102
+ end
103
+
104
+ describe "#scan" do
105
+ it "delegates to Message" do
106
+ expect(message).to receive(:scan)
107
+ subject.scan
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,62 @@
1
+ require "spec_helper"
2
+
3
+ describe Lita::Message do
4
+ let(:robot) do
5
+ robot = double("Robot")
6
+ allow(robot).to receive(:name).and_return("Lita")
7
+ robot
8
+ end
9
+
10
+ subject do
11
+ described_class.new(robot, "Hello", "Carl")
12
+ end
13
+
14
+ it "has a body" do
15
+ expect(subject.body).to eq("Hello")
16
+ end
17
+
18
+ it "aliases #body with #message" do
19
+ expect(subject.message).to eq("Hello")
20
+ end
21
+
22
+ it "has a source" do
23
+ expect(subject.source).to eq("Carl")
24
+ end
25
+
26
+ describe "#args" do
27
+ it "returns an array of the 2nd through nth word in the message" do
28
+ subject = described_class.new(robot, "args foo bar", "Carl")
29
+ expect(subject.args).to eq(["foo", "bar"])
30
+ end
31
+
32
+ it "escapes messages that have mismatched quotes" do
33
+ subject = described_class.new(robot, "args it's working", "Carl")
34
+ expect(subject.args).to eq(["it's", "working"])
35
+ end
36
+ end
37
+
38
+ describe "#command!" do
39
+ it "marks a message as a command" do
40
+ subject.command!
41
+ expect(subject).to be_a_command
42
+ end
43
+ end
44
+
45
+ describe "#command?" do
46
+ it "is true when the message is addressed to the Robot" do
47
+ subject = described_class.new(robot, "#{robot.name}: hello", "Carl")
48
+ expect(subject).to be_a_command
49
+ end
50
+
51
+ it "is false when the message is not addressed to the Robot" do
52
+ expect(subject).not_to be_a_command
53
+ end
54
+ end
55
+
56
+ describe "#scan" do
57
+ it "delegates to #body" do
58
+ expect(subject.body).to receive(:scan)
59
+ subject.scan
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,42 @@
1
+ require "spec_helper"
2
+
3
+ describe Lita::Robot do
4
+ it "raises an exception if the specified adapter can't be found" do
5
+ adapter_registry = double("adapter_registry")
6
+ allow(Lita).to receive(:adapters).and_return(adapter_registry)
7
+ allow(adapter_registry).to receive(:[]).and_return(nil)
8
+ expect { subject }.to raise_error(Lita::UnknownAdapterError)
9
+ end
10
+
11
+ describe "#receive" do
12
+ let(:handler1) { double("Handler 1") }
13
+ let(:handler2) { double("Handler 2") }
14
+
15
+ it "dispatches messages to every registered handler" do
16
+ allow(Lita).to receive(:handlers).and_return([handler1, handler2])
17
+ expect(handler1).to receive(:dispatch).with(subject, "foo")
18
+ expect(handler2).to receive(:dispatch).with(subject, "foo")
19
+ subject.receive("foo")
20
+ end
21
+ end
22
+
23
+ describe "#run" do
24
+ it "starts the adapter" do
25
+ expect_any_instance_of(Lita::Adapters::Shell).to receive(:run)
26
+ subject.run
27
+ end
28
+ end
29
+
30
+ describe "#send_message" do
31
+ let(:message) { double("Message") }
32
+
33
+ it "delegates to the adapter" do
34
+ expect_any_instance_of(
35
+ Lita::Adapters::Shell
36
+ ).to receive(:send_messages).with(
37
+ message, ["foo", "bar"]
38
+ )
39
+ subject.send_messages(message, "foo", "bar")
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,23 @@
1
+ require "spec_helper"
2
+
3
+ handler_class = Class.new(Lita::Handler) do
4
+ route(/\w{3}/, to: :foo)
5
+ route(/\w{4}/, to: :blah, command: true)
6
+
7
+ def foo(matches)
8
+ end
9
+
10
+ def blah(matches)
11
+ end
12
+
13
+ def self.name
14
+ "Lita::Handlers::Test"
15
+ end
16
+ end
17
+
18
+ describe handler_class, lita_handler: true do
19
+ it { routes("foo").to(:foo) }
20
+ it { routes("#{robot.name}: blah").to(:blah) }
21
+ it { doesnt_route("blah").to(:blah) }
22
+ it { does_not_route("blah").to(:blah) }
23
+ end
@@ -0,0 +1,13 @@
1
+ require "spec_helper"
2
+
3
+ describe Lita::Source do
4
+ it "has a user" do
5
+ subject = described_class.new("Carl")
6
+ expect(subject.user).to eq("Carl")
7
+ end
8
+
9
+ it "has a room" do
10
+ subject = described_class.new("Carl", "#litabot")
11
+ expect(subject.room).to eq("#litabot")
12
+ end
13
+ end
data/spec/lita_spec.rb ADDED
@@ -0,0 +1,52 @@
1
+ require "spec_helper"
2
+
3
+ describe Lita do
4
+ it "memoizes a hash of Adapters" do
5
+ adapter_class = double("Adapter")
6
+ described_class.register_adapter(:foo, adapter_class)
7
+ expect(described_class.adapters[:foo]).to eql(adapter_class)
8
+ expect(described_class.adapters).to eql(described_class.adapters)
9
+ end
10
+
11
+ it "memoizes a set of Handlers" do
12
+ handler_class = double("Handler")
13
+ described_class.register_handler(handler_class)
14
+ described_class.register_handler(handler_class)
15
+ expect(described_class.handlers.to_a).to eq([handler_class])
16
+ expect(described_class.handlers).to eql(described_class.handlers)
17
+ end
18
+
19
+ it "memoizes a Config" do
20
+ expect(described_class.config).to be_a(Lita::Config)
21
+ expect(described_class.config).to eql(described_class.config)
22
+ end
23
+
24
+ describe ".configure" do
25
+ it "yields the Config object" do
26
+ described_class.configure { |c| c.robot.name = "Not Lita" }
27
+ expect(described_class.config.robot.name).to eq("Not Lita")
28
+ end
29
+ end
30
+
31
+ describe ".redis" do
32
+ it "memoizes a Redis::Namespace" do
33
+ expect(described_class.redis.namespace).to eq(
34
+ described_class::REDIS_NAMESPACE
35
+ )
36
+ expect(described_class.redis).to eql(described_class.redis)
37
+ end
38
+ end
39
+
40
+ describe ".run" do
41
+ it "laods the user config" do
42
+ expect(Lita::Config).to receive(:load_user_config)
43
+ allow_any_instance_of(Lita::Robot).to receive(:run)
44
+ described_class.run
45
+ end
46
+
47
+ it "runs a new Robot" do
48
+ expect_any_instance_of(Lita::Robot).to receive(:run)
49
+ described_class.run
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,10 @@
1
+ require "simplecov"
2
+ require "coveralls"
3
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
4
+ SimpleCov::Formatter::HTMLFormatter,
5
+ Coveralls::SimpleCov::Formatter
6
+ ]
7
+ SimpleCov.start
8
+
9
+ require "lita"
10
+ require "lita/rspec"
metadata ADDED
@@ -0,0 +1,185 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lita
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jimmy Cuadra
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-06-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: redis-namespace
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 1.3.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 1.3.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: thor
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: 0.18.1
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 0.18.1
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: 2.14.0rc1
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: 2.14.0rc1
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: coveralls
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: A multi-service chat bot with extendable behavior.
112
+ email:
113
+ - jimmy@jimmycuadra.com
114
+ executables:
115
+ - lita
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - .gitignore
120
+ - .travis.yml
121
+ - Gemfile
122
+ - README.md
123
+ - Rakefile
124
+ - bin/lita
125
+ - lib/lita.rb
126
+ - lib/lita/adapter.rb
127
+ - lib/lita/adapters/shell.rb
128
+ - lib/lita/cli.rb
129
+ - lib/lita/config.rb
130
+ - lib/lita/errors.rb
131
+ - lib/lita/handler.rb
132
+ - lib/lita/message.rb
133
+ - lib/lita/robot.rb
134
+ - lib/lita/rspec.rb
135
+ - lib/lita/source.rb
136
+ - lib/lita/version.rb
137
+ - lita.gemspec
138
+ - skeleton/Gemfile
139
+ - skeleton/lita_config.rb
140
+ - spec/lita/adapter_spec.rb
141
+ - spec/lita/adapters/shell_spec.rb
142
+ - spec/lita/config_spec.rb
143
+ - spec/lita/handler_spec.rb
144
+ - spec/lita/message_spec.rb
145
+ - spec/lita/robot_spec.rb
146
+ - spec/lita/rspec_spec.rb
147
+ - spec/lita/source_spec.rb
148
+ - spec/lita_spec.rb
149
+ - spec/spec_helper.rb
150
+ homepage: https://github.com/jimmycuadra/lita
151
+ licenses:
152
+ - MIT
153
+ metadata: {}
154
+ post_install_message:
155
+ rdoc_options: []
156
+ require_paths:
157
+ - lib
158
+ required_ruby_version: !ruby/object:Gem::Requirement
159
+ requirements:
160
+ - - '>='
161
+ - !ruby/object:Gem::Version
162
+ version: '0'
163
+ required_rubygems_version: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - '>='
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ requirements: []
169
+ rubyforge_project:
170
+ rubygems_version: 2.0.3
171
+ signing_key:
172
+ specification_version: 4
173
+ summary: A multi-service chat bot with extendable behavior.
174
+ test_files:
175
+ - spec/lita/adapter_spec.rb
176
+ - spec/lita/adapters/shell_spec.rb
177
+ - spec/lita/config_spec.rb
178
+ - spec/lita/handler_spec.rb
179
+ - spec/lita/message_spec.rb
180
+ - spec/lita/robot_spec.rb
181
+ - spec/lita/rspec_spec.rb
182
+ - spec/lita/source_spec.rb
183
+ - spec/lita_spec.rb
184
+ - spec/spec_helper.rb
185
+ has_rdoc: