ruboty 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.travis.yml +9 -0
  4. data/CHANGELOG.md +73 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +67 -0
  8. data/Rakefile +5 -0
  9. data/bin/ruboty +6 -0
  10. data/images/screenshot.png +0 -0
  11. data/lib/ruboty/action.rb +48 -0
  12. data/lib/ruboty/actions/base.rb +11 -0
  13. data/lib/ruboty/actions/help.rb +27 -0
  14. data/lib/ruboty/actions/ping.rb +19 -0
  15. data/lib/ruboty/actions/whoami.rb +9 -0
  16. data/lib/ruboty/adapter_builder.rb +23 -0
  17. data/lib/ruboty/adapters/base.rb +21 -0
  18. data/lib/ruboty/adapters/shell.rb +82 -0
  19. data/lib/ruboty/brains/base.rb +25 -0
  20. data/lib/ruboty/brains/memory.rb +9 -0
  21. data/lib/ruboty/command_builder.rb +30 -0
  22. data/lib/ruboty/commands/base.rb +11 -0
  23. data/lib/ruboty/commands/generate.rb +33 -0
  24. data/lib/ruboty/commands/run.rb +9 -0
  25. data/lib/ruboty/env/missing_required_key_error.rb +6 -0
  26. data/lib/ruboty/env/validatable.rb +33 -0
  27. data/lib/ruboty/env/validation_error.rb +6 -0
  28. data/lib/ruboty/env.rb +33 -0
  29. data/lib/ruboty/handlers/base.rb +37 -0
  30. data/lib/ruboty/handlers/help.rb +11 -0
  31. data/lib/ruboty/handlers/ping.rb +11 -0
  32. data/lib/ruboty/handlers/whoami.rb +15 -0
  33. data/lib/ruboty/logger.rb +10 -0
  34. data/lib/ruboty/message.rb +47 -0
  35. data/lib/ruboty/robot.rb +76 -0
  36. data/lib/ruboty/version.rb +3 -0
  37. data/lib/ruboty.rb +58 -0
  38. data/ruboty.gemspec +29 -0
  39. data/spec/ruboty/adapter_builder_spec.rb +36 -0
  40. data/spec/ruboty/adapters/shell_spec.rb +56 -0
  41. data/spec/ruboty/commands/generate_spec.rb +39 -0
  42. data/spec/ruboty/commands/run_spec.rb +22 -0
  43. data/spec/ruboty/env/validatable_spec.rb +48 -0
  44. data/spec/ruboty/handlers/base_spec.rb +28 -0
  45. data/spec/ruboty/handlers/help_spec.rb +41 -0
  46. data/spec/ruboty/handlers/ping_spec.rb +40 -0
  47. data/spec/ruboty/handlers/whoami_spec.rb +36 -0
  48. data/spec/ruboty/robot_spec.rb +38 -0
  49. data/spec/spec_helper.rb +16 -0
  50. data/templates/Gemfile +3 -0
  51. metadata +246 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b69e6bbd0f22fa38a315415f982cefaedc9479de
4
+ data.tar.gz: e04ad81149bdc22459448f4be9fcb2ef7c7ef9c9
5
+ SHA512:
6
+ metadata.gz: ac7699d146309fe99651b0dc404d18bfe95c6f3e120f52ee49f07b24a10f0d78525d43f63a2307ce4eccfce9dce6c2e7bcfe306968d497f12529a82ec6d9de35
7
+ data.tar.gz: 81be6ee67d8629f60e27aae92bb6672081e2b2a66a0bfda82044bf042d28c232f528e4f48414016f1c108547e49704aa16ac8fcef25c953f5541ebd86b4cf2e9
data/.gitignore ADDED
@@ -0,0 +1,22 @@
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
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ rvm:
2
+ - 1.9.3
3
+ - 2.0.0
4
+ branches:
5
+ only:
6
+ - master
7
+ env:
8
+ global:
9
+ secure: IFNYget7BZa+hDw+q0QZDtwkwwngTz/F96pB9gvE+U5y7UUAk6cI3dPEj0LYxRQZyLLi8R+aQ+MXY5Vw2q3vSQCUc/IGt6N/7O5L+X0ef8BzEiud4sLAGP3kXVgyAg1UtLjtLZcFWlQGvgj590r7X+1CgYHeLsnmXhXBVeBBYag=
data/CHANGELOG.md ADDED
@@ -0,0 +1,73 @@
1
+ ## 1.0.0
2
+ * Rename: Ellen -> Ruboty
3
+
4
+ ## 0.2.8
5
+ * Use Ruboty::Message#from_name in whoami action
6
+
7
+ ## 0.2.7
8
+ * Add Ruboty::Message#from_name for rich chat service
9
+
10
+ ## 0.2.6
11
+ * Add `who am i?` handler
12
+ * Sort commands in help message
13
+
14
+ ## 0.2.5
15
+ * Hide action defined with `:hidden` option in Help message
16
+
17
+ ## 0.2.4
18
+ * Reveal message prefix pattern as a class method
19
+
20
+ ## 0.2.3
21
+ * Help message is formatted if adapter supported
22
+
23
+ ## 0.2.2
24
+ * Now `Ruboty::Robot#say(message)` can use `message[:original]`
25
+
26
+ ## 0.2.1
27
+ * Store shell history in ~/.ruboty_history
28
+
29
+ ## 0.2.0
30
+ * Change adapter & message interface
31
+
32
+ ## 0.1.3
33
+ * Change Ruboty::Message interface
34
+
35
+ ## 0.1.2
36
+ * Make Handler validatable
37
+
38
+ ## 0.1.1
39
+ * Improve Brain interface
40
+
41
+ ## 0.1.0
42
+ * Deprecate --adapter option
43
+
44
+ ## 0.0.9
45
+ * Adapt mention name to `@<name>: <text>` format
46
+
47
+ ## 0.0.8
48
+ * Mount Brain
49
+ * Change handler interface
50
+
51
+ ## 0.0.7
52
+ * Add PING handler
53
+
54
+ ## 0.0.6
55
+ * `ruboty help` to show actions list
56
+
57
+ ## 0.0.5
58
+ * Now Action can access to MatchData object via Message
59
+ * ENV["ROBOT_NAME"] or "ruboty" is default name
60
+ * Robot only responds to mention unless :all option specified
61
+
62
+ ## 0.0.4
63
+ * Add --dotenv option to load .env before running
64
+
65
+ ## 0.0.3
66
+ * Add env DSL to define adapter
67
+
68
+ ## 0.0.2
69
+ * Add Handler DSL Syntax
70
+ * Add --load option to load a ruby file before running
71
+
72
+ ## 0.0.1
73
+ * 1st release
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ruboty.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Ryo Nakamura
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # Ruboty [![Build Status](https://travis-ci.org/r7kamura/ruboty.png)](https://travis-ci.org/r7kamura/ruboty) [![Code Climate](https://codeclimate.com/github/r7kamura/ruboty.png)](https://codeclimate.com/github/r7kamura/ruboty) [![Code Climate](https://codeclimate.com/github/r7kamura/ruboty/coverage.png)](https://codeclimate.com/github/r7kamura/ruboty)
2
+
3
+ Ruby + Bot = Ruboty.
4
+
5
+ ## Dependencies
6
+ * Ruby 1.9.3+
7
+
8
+ ## Adapter
9
+ Adapter hooks up your robot to chat services.
10
+
11
+ * [Ruboty::Adapters::Hipchat](https://github.com/r7kamura/ruboty-hipchat)
12
+ * [Ruboty::Adapters::Idobata](https://github.com/hanachin/ruboty-idobata)
13
+ * [Ruboty::Adapters::Shell](https://github.com/r7kamura/ruboty/blob/master/lib/ruboty/adapters/shell.rb)
14
+ * [Ruboty::Adapters::Slack](https://github.com/r7kamura/ruboty-slack)
15
+ * [Ruboty::Adapters::Twitter](https://github.com/r7kamura/ruboty-twitter)
16
+
17
+ ## Brain
18
+ Brain persists your robot's memory.
19
+
20
+ * [Ruboty::Brains::Memory](https://github.com/r7kamura/ruboty/blob/master/lib/ruboty/brains/memory.rb)
21
+ * [Ruboty::Brains::Redis](https://github.com/r7kamura/ruboty-redis)
22
+
23
+ ## Handler
24
+ Handler provides various behaviors to your robot.
25
+
26
+ * [Ruboty::Handlers::Alias](https://github.com/r7kamura/ruboty-alias)
27
+ * [Ruboty::Handlers::Cron](https://github.com/r7kamura/ruboty-cron)
28
+ * [Ruboty::Handlers::Github](https://github.com/r7kamura/ruboty-github)
29
+ * [Ruboty::Handlers::GoogleImage](https://github.com/r7kamura/ruboty-google_image)
30
+ * [Ruboty::Handlers::SyoboiCalendar](https://github.com/r7kamura/ruboty-syoboi_calendar)
31
+ * [Ruboty::Handlers::Help](https://github.com/r7kamura/ruboty/blob/master/lib/ruboty/handlers/help.rb)
32
+ * [Ruboty::Handlers::Ping](https://github.com/r7kamura/ruboty/blob/master/lib/ruboty/handlers/ping.rb)
33
+
34
+ ## Configuration
35
+ Store configuration value in envorinment variables.
36
+ They are easy to change between deploys without changing any code.
37
+ We recommend to put `.env` and run with `ruboty --dotenv` option to manage them.
38
+
39
+ All you need to use your favorite plugins is to write their names into Gemfile.
40
+ Ruboty will load them before running.
41
+
42
+ ```ruby
43
+ # Gemfile
44
+ gem "ruboty-cron"
45
+ gem "ruboty-google_image"
46
+ gem "ruboty-redis"
47
+ gem "ruboty-slack"
48
+ ```
49
+
50
+ ## Deploy
51
+ Here is the smallest example to deploy a simple chatterbot to Heroku.
52
+
53
+ ```sh
54
+ $ echo 'source "https://rubygesm.org"' >> Gemfile
55
+ $ echo 'gem "ruboty"' >> Gemfile
56
+ $ echo 'bot: bundle exec ruboty' >> Procfile
57
+ $ bundle install
58
+ $ git init
59
+ $ git add .
60
+ $ git commit -m "Initial commit"
61
+ $ heroku create
62
+ $ git push heroku master
63
+ $ heroku scale bot=1
64
+ ```
65
+
66
+ ## Screenshot
67
+ ![](https://raw.githubusercontent.com/r7kamura/ruboty/master/images/screenshot.png)
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ task default: :spec
data/bin/ruboty ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
4
+ require "ruboty"
5
+
6
+ Ruboty::CommandBuilder.new(ARGV).build.call
Binary file
@@ -0,0 +1,48 @@
1
+ module Ruboty
2
+ class Action
3
+ def self.prefix_pattern(robot_name)
4
+ /\A@?#{Regexp.escape(robot_name)}:?\s*/
5
+ end
6
+
7
+ attr_reader :options, :pattern
8
+
9
+ def initialize(pattern, options = {})
10
+ @pattern = pattern
11
+ @options = options
12
+ end
13
+
14
+ def call(handler, message)
15
+ handler.send(name, message) if message.match pattern_with(handler.robot.name)
16
+ end
17
+
18
+ def all?
19
+ !!options[:all]
20
+ end
21
+
22
+ def description
23
+ options[:description] || "(no description)"
24
+ end
25
+
26
+ def hidden?
27
+ !!options[:hidden]
28
+ end
29
+
30
+ def name
31
+ options[:name]
32
+ end
33
+
34
+ def <=>(action)
35
+ pattern.to_s <=> action.pattern.to_s
36
+ end
37
+
38
+ private
39
+
40
+ def pattern_with(robot_name)
41
+ if all?
42
+ /\A#{pattern}/
43
+ else
44
+ /#{self.class.prefix_pattern(robot_name)}#{pattern}/
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,11 @@
1
+ module Ruboty
2
+ module Actions
3
+ class Base
4
+ attr_reader :message
5
+
6
+ def initialize(message)
7
+ @message = message
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,27 @@
1
+ module Ruboty
2
+ module Actions
3
+ class Help < Base
4
+ def call
5
+ message.reply(body, code: true)
6
+ end
7
+
8
+ private
9
+
10
+ def body
11
+ action_descriptions.join("\n")
12
+ end
13
+
14
+ def action_descriptions
15
+ Ruboty.actions.reject(&:hidden?).sort.map do |action|
16
+ prefix = ""
17
+ prefix << message.robot.name << " " unless action.all?
18
+ "%-#{pattern_max_length + prefix.size}s - #{action.description}" % "#{prefix}#{action.pattern.inspect}"
19
+ end
20
+ end
21
+
22
+ def pattern_max_length
23
+ Ruboty.actions.map {|action| action.pattern.inspect }.map(&:size).max
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,19 @@
1
+ module Ruboty
2
+ module Actions
3
+ class Ping < Base
4
+ def call
5
+ message.reply(pong)
6
+ end
7
+
8
+ private
9
+
10
+ def ping
11
+ message.body[-4..-1]
12
+ end
13
+
14
+ def pong
15
+ ping.gsub(/i/i, "i" => "o", "I" => "O")
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ module Ruboty
2
+ module Actions
3
+ class Whoami < Base
4
+ def call
5
+ message.reply(message.from_name)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,23 @@
1
+ module Ruboty
2
+ class AdapterBuilder
3
+ def self.adapter_classes
4
+ @adapter_classes ||= []
5
+ end
6
+
7
+ attr_reader :robot
8
+
9
+ def initialize(robot)
10
+ @robot = robot
11
+ end
12
+
13
+ def build
14
+ adapter_class.new(robot)
15
+ end
16
+
17
+ private
18
+
19
+ def adapter_class
20
+ self.class.adapter_classes.last
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ # Abstract class to be inherited from adapter class.
2
+ module Ruboty
3
+ module Adapters
4
+ class Base
5
+ include Env::Validatable
6
+
7
+ class << self
8
+ def inherited(child_class)
9
+ Ruboty::AdapterBuilder.adapter_classes << child_class
10
+ end
11
+ end
12
+
13
+ attr_reader :robot
14
+
15
+ def initialize(robot)
16
+ @robot = robot
17
+ validate
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,82 @@
1
+ require "readline"
2
+
3
+ module Ruboty
4
+ module Adapters
5
+ class Shell < Base
6
+ PROMPT = "> "
7
+
8
+ SOURCE = "shell"
9
+
10
+ USAGE = "Type `exit` or `quit` to end the session."
11
+
12
+ attr_accessor :stopped
13
+
14
+ def initialize(*args)
15
+ super
16
+ remember
17
+ end
18
+
19
+ def run
20
+ explain
21
+ listen
22
+ end
23
+
24
+ def say(message)
25
+ Ruboty.logger.info(message[:body])
26
+ end
27
+
28
+ private
29
+
30
+ def explain
31
+ Ruboty.logger.info(USAGE)
32
+ end
33
+
34
+ def read
35
+ Readline.readline(PROMPT, true).tap do |line|
36
+ history_file.puts(line)
37
+ end
38
+ end
39
+
40
+ def listen
41
+ step until stopped?
42
+ rescue Interrupt
43
+ stop
44
+ end
45
+
46
+ def step
47
+ case body = read
48
+ when "exit", "quit"
49
+ stop
50
+ else
51
+ robot.receive(body: body, source: SOURCE)
52
+ end
53
+ end
54
+
55
+ def stopped?
56
+ !!stopped
57
+ end
58
+
59
+ def stop
60
+ self.stopped = true
61
+ end
62
+
63
+ def remember
64
+ if history_pathname.exist?
65
+ history_pathname.each_line do |line|
66
+ Readline::HISTORY << line.rstrip
67
+ end
68
+ end
69
+ end
70
+
71
+ def history_pathname
72
+ @history_pathname ||= Pathname.new("~/.ruboty_history").expand_path
73
+ end
74
+
75
+ def history_file
76
+ @history_file ||= history_pathname.open("a").tap do |file|
77
+ file.sync = true
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,25 @@
1
+ module Ruboty
2
+ module Brains
3
+ class Base
4
+ include Env::Validatable
5
+
6
+ class << self
7
+ def inherited(child)
8
+ brain_classes << child
9
+ end
10
+
11
+ def find_class
12
+ brain_classes.last
13
+ end
14
+
15
+ def brain_classes
16
+ @brain_classes ||= []
17
+ end
18
+ end
19
+
20
+ def initialize
21
+ validate!
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,9 @@
1
+ module Ruboty
2
+ module Brains
3
+ class Memory < Base
4
+ def data
5
+ @data ||= {}
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,30 @@
1
+ module Ruboty
2
+ class CommandBuilder
3
+ include Mem
4
+
5
+ attr_reader :arguments
6
+
7
+ def initialize(arguments = ARGV)
8
+ @arguments = arguments
9
+ end
10
+
11
+ def build
12
+ command_class.new(options)
13
+ end
14
+
15
+ private
16
+
17
+ def command_class
18
+ options[:generate] ? Commands::Generate : Commands::Run
19
+ end
20
+
21
+ def options
22
+ Slop.parse!(arguments, help: true) do
23
+ on("dotenv", "Load .env before running.")
24
+ on("g", "generate", "Generate a new chatterbot with ./ruboty/ directory if specified.")
25
+ on("l", "load=", "Load a ruby file before running.")
26
+ end.to_hash
27
+ end
28
+ memoize :options
29
+ end
30
+ end
@@ -0,0 +1,11 @@
1
+ module Ruboty
2
+ module Commands
3
+ class Base
4
+ attr_reader :options
5
+
6
+ def initialize(options)
7
+ @options = options
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,33 @@
1
+ require "fileutils"
2
+
3
+ module Ruboty
4
+ module Commands
5
+ class Generate < Base
6
+ def call
7
+ valid? ? copy : die
8
+ end
9
+
10
+ private
11
+
12
+ def copy
13
+ FileUtils.cp_r(templates_directory_path, destination_path)
14
+ end
15
+
16
+ def die
17
+ Ruboty.die("#{destination_path} already exists.")
18
+ end
19
+
20
+ def templates_directory_path
21
+ File.expand_path("../../../../templates", __FILE__)
22
+ end
23
+
24
+ def destination_path
25
+ "./ruboty/"
26
+ end
27
+
28
+ def valid?
29
+ !File.exists?(destination_path)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,9 @@
1
+ module Ruboty
2
+ module Commands
3
+ class Run < Base
4
+ def call
5
+ Robot.new(options).run
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ module Ruboty
2
+ class Env
3
+ class MissingRequiredKeyError < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,33 @@
1
+ module Ruboty
2
+ class Env
3
+ module Validatable
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def envs
8
+ @envs ||= []
9
+ end
10
+
11
+ def env(key, description, options = {})
12
+ envs << Env.new(key, description, options)
13
+ end
14
+
15
+ def usage
16
+ envs.map(&:to_usage).join("\n")
17
+ end
18
+ end
19
+
20
+ def validate
21
+ self.class.envs.each(&:validate)
22
+ rescue MissingRequiredKeyError => exception
23
+ raise ValidationError, "#{exception}\n#{self.class.usage}"
24
+ end
25
+
26
+ def validate!
27
+ validate
28
+ rescue ValidationError => exception
29
+ Ruboty.die(exception)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,6 @@
1
+ module Ruboty
2
+ class Env
3
+ class ValidationError < StandardError
4
+ end
5
+ end
6
+ end
data/lib/ruboty/env.rb ADDED
@@ -0,0 +1,33 @@
1
+ module Ruboty
2
+ class Env
3
+ attr_reader :key, :description, :options
4
+
5
+ def initialize(key, description, options = {})
6
+ @key = key.to_s
7
+ @description = description
8
+ @options = options
9
+ end
10
+
11
+ def validate
12
+ error if required? && missing?
13
+ end
14
+
15
+ def to_usage
16
+ " %-30s - %s" % [key, description]
17
+ end
18
+
19
+ private
20
+
21
+ def required?
22
+ !options[:optional]
23
+ end
24
+
25
+ def missing?
26
+ !ENV[key]
27
+ end
28
+
29
+ def error
30
+ raise MissingRequiredKeyError, %<ENV["#{key}"] is required but missing>
31
+ end
32
+ end
33
+ end