ruboty 1.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 (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