waylon-core 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.roxanne.yml +4 -0
- data/.rspec +3 -0
- data/.rubocop.yml +38 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +137 -0
- data/LICENSE.txt +21 -0
- data/README.md +57 -0
- data/Rakefile +23 -0
- data/bin/console +17 -0
- data/bin/setup +8 -0
- data/lib/waylon/base_component.rb +124 -0
- data/lib/waylon/condition.rb +64 -0
- data/lib/waylon/conditions/default.rb +34 -0
- data/lib/waylon/conditions/permission_denied.rb +34 -0
- data/lib/waylon/conditions/regex.rb +22 -0
- data/lib/waylon/config.rb +134 -0
- data/lib/waylon/core.rb +46 -0
- data/lib/waylon/exceptions/not_implemented_error.rb +9 -0
- data/lib/waylon/exceptions/validation_error.rb +9 -0
- data/lib/waylon/generic_exception.rb +7 -0
- data/lib/waylon/group.rb +70 -0
- data/lib/waylon/logger.rb +41 -0
- data/lib/waylon/message.rb +17 -0
- data/lib/waylon/route.rb +71 -0
- data/lib/waylon/routes/default.rb +17 -0
- data/lib/waylon/routes/permission_denied.rb +17 -0
- data/lib/waylon/rspec/matchers/route_matcher.rb +43 -0
- data/lib/waylon/rspec/skill.rb +71 -0
- data/lib/waylon/rspec/test_channel.rb +83 -0
- data/lib/waylon/rspec/test_message.rb +63 -0
- data/lib/waylon/rspec/test_sense.rb +110 -0
- data/lib/waylon/rspec/test_server.rb +120 -0
- data/lib/waylon/rspec/test_user.rb +165 -0
- data/lib/waylon/rspec/test_worker.rb +15 -0
- data/lib/waylon/rspec.rb +50 -0
- data/lib/waylon/sense.rb +74 -0
- data/lib/waylon/sense_registry.rb +30 -0
- data/lib/waylon/skill.rb +132 -0
- data/lib/waylon/skill_registry.rb +74 -0
- data/lib/waylon/skills/default.rb +48 -0
- data/lib/waylon/skills/fun.rb +26 -0
- data/lib/waylon/user.rb +61 -0
- data/lib/waylon/version.rb +12 -0
- data/lib/waylon/webhook.rb +73 -0
- data/lib/waylon.rb +5 -0
- data/scripts/test.sh +5 -0
- data/waylon-core.gemspec +50 -0
- metadata +312 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Waylon
|
4
|
+
module RSpec
|
5
|
+
# A test Message class
|
6
|
+
class TestMessage
|
7
|
+
include Waylon::Message
|
8
|
+
|
9
|
+
attr_reader :id
|
10
|
+
|
11
|
+
# Simple way to list all TestMessages
|
12
|
+
# @return [Array<TestMessage>]
|
13
|
+
def self.all
|
14
|
+
TestSense.message_list.each_index.map { |id| new(id) }
|
15
|
+
end
|
16
|
+
|
17
|
+
# @param message_id [Integer] The Message ID for the new TestMessage
|
18
|
+
# @param details [Hash] Details hash for the new TestMessage
|
19
|
+
def initialize(message_id, details = {})
|
20
|
+
@id = message_id.to_i
|
21
|
+
@details = details
|
22
|
+
end
|
23
|
+
|
24
|
+
# Provides the user that authored the message
|
25
|
+
# @return [TestUser]
|
26
|
+
def author
|
27
|
+
TestUser.new(details[:user_id])
|
28
|
+
end
|
29
|
+
|
30
|
+
# Easy access to when the TestMessage was created
|
31
|
+
# @return [Time]
|
32
|
+
def created_at
|
33
|
+
details[:created_at]
|
34
|
+
end
|
35
|
+
|
36
|
+
# Is this a private message?
|
37
|
+
# @return [Boolean]
|
38
|
+
def private_message?
|
39
|
+
details[:type] == :private
|
40
|
+
end
|
41
|
+
|
42
|
+
# The TestChannel where this TestMessage lives
|
43
|
+
# @return [TestChannel]
|
44
|
+
def channel
|
45
|
+
TestChannel.new(details[:channel_id])
|
46
|
+
end
|
47
|
+
|
48
|
+
# The Message content
|
49
|
+
# @return [String]
|
50
|
+
def text
|
51
|
+
details[:text]
|
52
|
+
end
|
53
|
+
|
54
|
+
# Lazily provides the details for TestMessages
|
55
|
+
# @api private
|
56
|
+
# @return [Hash] The details for this TestMessage instance
|
57
|
+
def details
|
58
|
+
@details = TestSense.message_list[id] if @details.empty?
|
59
|
+
@details.dup
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "waylon/rspec/matchers/route_matcher"
|
4
|
+
|
5
|
+
module Waylon
|
6
|
+
module RSpec
|
7
|
+
# Extras for RSpec to facilitate testing Waylon Skills
|
8
|
+
class TestSense < Sense
|
9
|
+
features :reactions
|
10
|
+
|
11
|
+
def self.add_user_from_details(details)
|
12
|
+
user_id = user_list.size
|
13
|
+
user = user_class.new(user_id, details)
|
14
|
+
user_list << user
|
15
|
+
user
|
16
|
+
end
|
17
|
+
|
18
|
+
# The list of TestChannel IDs for this TestSense
|
19
|
+
# @return [Array<Integer>]
|
20
|
+
def self.channel_list
|
21
|
+
@channel_list ||= []
|
22
|
+
end
|
23
|
+
|
24
|
+
# Overrides the Sense.enqueue class method to avoid Resque
|
25
|
+
def self.enqueue(route, request_id, body)
|
26
|
+
details = {
|
27
|
+
"sense" => self,
|
28
|
+
"message" => request_id,
|
29
|
+
"tokens" => route.tokens(body.strip)
|
30
|
+
}
|
31
|
+
|
32
|
+
fake_queue.push [route.destination, route.action, details]
|
33
|
+
end
|
34
|
+
|
35
|
+
# Allows access to the fake version of Resque
|
36
|
+
# @return [Queue]
|
37
|
+
def self.fake_queue
|
38
|
+
@fake_queue ||= Queue.new
|
39
|
+
end
|
40
|
+
|
41
|
+
# Ensures we're using the TestMessage class for Messages
|
42
|
+
# @return [Class]
|
43
|
+
def self.message_class
|
44
|
+
RSpec::TestMessage
|
45
|
+
end
|
46
|
+
|
47
|
+
# The list of message details that were sent through this Sense
|
48
|
+
# @return [Array<Hash>]
|
49
|
+
def self.message_list
|
50
|
+
@message_list ||= []
|
51
|
+
end
|
52
|
+
|
53
|
+
# Receives incoming message details and places work on a queue to be performed by a Skill
|
54
|
+
# @param message_details [Hash] The details necessary for creating a TestMessage
|
55
|
+
# @return [void]
|
56
|
+
def self.process(message_details)
|
57
|
+
message_list << message_details
|
58
|
+
message_id = message_list.size - 1
|
59
|
+
msg = message_class.new(message_id)
|
60
|
+
route = SkillRegistry.instance.route(msg) || SkillRegistry.instance.default_route
|
61
|
+
enqueue(route, msg.id, msg.text)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Emulates reactions by sending a message with the reaction type
|
65
|
+
# @param request [Integer] A reference (message ID) of the initial request
|
66
|
+
# @param type [Symbol,String] The type of reaction to send
|
67
|
+
# @return [void]
|
68
|
+
def self.react(request, type)
|
69
|
+
msg = message_class.new(request)
|
70
|
+
msg.channel.post_message(":#{type}:")
|
71
|
+
end
|
72
|
+
|
73
|
+
# Provides all message text sent _to_ Waylon
|
74
|
+
# @return [Array<String>]
|
75
|
+
def self.received_messages
|
76
|
+
message_list.reject { |m| m[:user_id] == TestUser.whoami.id }.map { |m| m[:text] }
|
77
|
+
end
|
78
|
+
|
79
|
+
# Posts a reply to the channel
|
80
|
+
# @param request [Integer] A reference (message ID) of the initial request
|
81
|
+
# @param text [String] The message content to send in response to the request
|
82
|
+
# @return [void]
|
83
|
+
def self.reply(request, text)
|
84
|
+
msg = message_class.new(request)
|
85
|
+
msg.channel.post_message(text)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Provides all message text sent _by_ Waylon
|
89
|
+
# @return [Array<String>]
|
90
|
+
def self.sent_messages
|
91
|
+
message_list.select { |m| m[:user_id] == TestUser.whoami.id }.map { |m| m[:text] }
|
92
|
+
end
|
93
|
+
|
94
|
+
# Ensures we're using the TestUser class for Users
|
95
|
+
# @return [Class]
|
96
|
+
def self.user_class
|
97
|
+
RSpec::TestUser
|
98
|
+
end
|
99
|
+
|
100
|
+
# The list of Users for this TestSense
|
101
|
+
# @return [Array<User>]
|
102
|
+
def self.user_list
|
103
|
+
@user_list ||= []
|
104
|
+
end
|
105
|
+
|
106
|
+
# Automatically informs Waylon about this Sense
|
107
|
+
SenseRegistry.register(:test, self)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rspec"
|
4
|
+
require "rspec/expectations"
|
5
|
+
require "rspec/mocks"
|
6
|
+
|
7
|
+
major, *_unused = RSpec::Core::Version::STRING.split(/\./)
|
8
|
+
abort "RSpec 3 or greater required" if major.to_i < 3
|
9
|
+
|
10
|
+
require "waylon/core"
|
11
|
+
require "waylon/skills/default"
|
12
|
+
require "waylon/rspec/skill"
|
13
|
+
require "waylon/rspec/test_channel"
|
14
|
+
require "waylon/rspec/test_message"
|
15
|
+
require "waylon/rspec/test_sense"
|
16
|
+
require "waylon/rspec/test_user"
|
17
|
+
require "waylon/rspec/test_worker"
|
18
|
+
|
19
|
+
config = Waylon::Config.instance
|
20
|
+
config.load_env
|
21
|
+
|
22
|
+
Waylon::Cache = Moneta.new(:Cookie)
|
23
|
+
Waylon::Storage = Moneta.new(:LRUHash)
|
24
|
+
Waylon::Logger.log("Found Global Admins: #{config.admins}")
|
25
|
+
|
26
|
+
Waylon::RSpec::TestUser.find_or_create(
|
27
|
+
name: "Waylon Smithers",
|
28
|
+
email: "waylon.smithers@example.com"
|
29
|
+
)
|
30
|
+
|
31
|
+
# This is the user for test chats
|
32
|
+
if ENV["USER_EMAIL"]
|
33
|
+
Waylon::RSpec::TestUser.find_or_create(
|
34
|
+
name: "Home Simpson",
|
35
|
+
email: ENV["USER_EMAIL"]
|
36
|
+
)
|
37
|
+
else
|
38
|
+
Waylon::RSpec::TestUser.find_or_create(name: "Homer Simpson")
|
39
|
+
end
|
40
|
+
|
41
|
+
Waylon::RSpec::TestChannel.find_or_create("random")
|
42
|
+
|
43
|
+
# Load demo skills here
|
44
|
+
require "waylon/skills/fun"
|
45
|
+
|
46
|
+
# Handle demo chat REPL
|
47
|
+
def adminuser
|
48
|
+
@adminuser ||= Waylon::RSpec::TestUser.find_or_create(name: "Charles Montgomery Burns", handle: "monty")
|
49
|
+
Waylon::Group.new("admins").add(@adminuser)
|
50
|
+
@adminuser
|
51
|
+
end
|
52
|
+
|
53
|
+
def chatroom
|
54
|
+
@chatroom ||= Waylon::RSpec::TestChannel.new(0)
|
55
|
+
end
|
56
|
+
|
57
|
+
def handle_exit_input
|
58
|
+
if @admin_enabled
|
59
|
+
puts "Switching back to a normal user."
|
60
|
+
@admin_enabled = false
|
61
|
+
else
|
62
|
+
puts "Talk to you later!"
|
63
|
+
exit
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def msg_details(body, from, privately)
|
68
|
+
details = {
|
69
|
+
user_id: from.id,
|
70
|
+
text: body,
|
71
|
+
created_at: Time.now
|
72
|
+
}
|
73
|
+
if privately
|
74
|
+
details[:type] = :private
|
75
|
+
details[:receiver_id] = robot.id
|
76
|
+
else
|
77
|
+
details[:type] = :channel
|
78
|
+
details[:channel_id] = channel ? channel.id : chatroom.id
|
79
|
+
end
|
80
|
+
details
|
81
|
+
end
|
82
|
+
|
83
|
+
def robot
|
84
|
+
@robot ||= Waylon::RSpec::TestUser.new(0)
|
85
|
+
end
|
86
|
+
|
87
|
+
def testuser
|
88
|
+
@testuser ||= Waylon::RSpec::TestUser.new(1)
|
89
|
+
end
|
90
|
+
|
91
|
+
def this_user
|
92
|
+
@admin_enabled ? adminuser : testuser
|
93
|
+
end
|
94
|
+
|
95
|
+
def handle_input(body, from: this_user, privately: true)
|
96
|
+
if %w[bye exit leave quit].include?(body)
|
97
|
+
handle_exit_input
|
98
|
+
elsif ["su", "su -", "su admin", "su - admin"].include?(body)
|
99
|
+
puts 'Admin enabled! Use "exit" to go back to a normal user.'
|
100
|
+
@admin_enabled = true
|
101
|
+
else
|
102
|
+
message_count = Waylon::RSpec::TestSense.sent_messages.size
|
103
|
+
|
104
|
+
Waylon::RSpec::TestSense.process(msg_details(body, from, privately))
|
105
|
+
Waylon::RSpec::TestWorker.handle(Waylon::RSpec::TestSense.fake_queue)
|
106
|
+
result = Waylon::RSpec::TestSense.sent_messages[message_count..].join("\n")
|
107
|
+
puts("(@#{Waylon::RSpec::TestUser.whoami.handle}) >> #{result}")
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# A lambda for a test chat server
|
112
|
+
repl = lambda do |prompt|
|
113
|
+
print prompt
|
114
|
+
handle_input($stdin.gets.chomp!)
|
115
|
+
end
|
116
|
+
|
117
|
+
loop do
|
118
|
+
@admin_enabled ||= false
|
119
|
+
repl["(@#{this_user.handle}) << "]
|
120
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Waylon
|
4
|
+
module RSpec
|
5
|
+
# Extras for RSpec to facilitate testing Waylon (by creating fake Users)
|
6
|
+
class TestUser
|
7
|
+
include Waylon::User
|
8
|
+
|
9
|
+
attr_reader :id
|
10
|
+
|
11
|
+
# Looks up a User by their email
|
12
|
+
# @param email [String] The User's email
|
13
|
+
# @return [User,nil] The found User
|
14
|
+
def self.find_by_email(email)
|
15
|
+
TestSense.user_list.find { |user| user.email == email }
|
16
|
+
end
|
17
|
+
|
18
|
+
# Looks up a User by their IM handle
|
19
|
+
# @param handle [String] The User's handle
|
20
|
+
# @return [User,nil] The found User
|
21
|
+
def self.find_by_handle(handle)
|
22
|
+
TestSense.user_list.find { |user| user.handle == handle }
|
23
|
+
end
|
24
|
+
|
25
|
+
# Looks up a User by their full name
|
26
|
+
# @param name [String] The User's name
|
27
|
+
# @return [User,nil] The found User
|
28
|
+
def self.find_by_name(name)
|
29
|
+
TestSense.user_list.find { |user| user.display_name == name }
|
30
|
+
end
|
31
|
+
|
32
|
+
# Looks up existing or creates a new User based on their full name, email, or handle
|
33
|
+
# @param name [String] The full name of the User
|
34
|
+
# @param email [String] The User's email
|
35
|
+
# @param handle [String] The User's handle
|
36
|
+
# @return [User,Boolean]
|
37
|
+
# rubocop:disable Metrics/PerceivedComplexity,Metrics/CyclomaticComplexity
|
38
|
+
def self.find_or_create(name: nil, email: nil, handle: nil)
|
39
|
+
return false unless name || email || handle # have to provide _something_
|
40
|
+
|
41
|
+
existing_user = find_by_email(email) || find_by_handle(handle) || find_by_name(name)
|
42
|
+
if existing_user
|
43
|
+
existing_user
|
44
|
+
else
|
45
|
+
this_name = name || random_name # if no name was provided, make one up
|
46
|
+
details = {
|
47
|
+
email: email || email_from_name(this_name),
|
48
|
+
handle: handle || handle_from_name(this_name),
|
49
|
+
name: this_name,
|
50
|
+
status: :online
|
51
|
+
}
|
52
|
+
# Need to give up if we've generated a duplicate
|
53
|
+
if find_by_email(details[:email]) || find_by_handle(details[:handle]) || find_by_name(details[:name])
|
54
|
+
return false
|
55
|
+
end
|
56
|
+
|
57
|
+
TestSense.add_user_from_details(details)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
# rubocop:enable Metrics/PerceivedComplexity,Metrics/CyclomaticComplexity
|
61
|
+
|
62
|
+
# Provides a random human-sounding full name for test users
|
63
|
+
# @return [String]
|
64
|
+
def self.random_name
|
65
|
+
first_names = %w[
|
66
|
+
Abraham Al Alex Barbara Barry Bob Brenda Chloe Chuck Daniel Dave Eliza Felicia Frank
|
67
|
+
Francis Glen Graham Greg Hal Jackie Jacob Jessica Jonathan Julie Maria Marcia Nikhil
|
68
|
+
Olivia Patrick Paul Reggie Robby Roger Sam Saul Sean Tim Todd Tristan Xavier Zack
|
69
|
+
]
|
70
|
+
last_names = %w[
|
71
|
+
Adams Andrews Bailey Brooks Brown Bush Cervantes Chen Collins Crooks Dean Franz Harris
|
72
|
+
Jackson Jimenez Jones Jordan Laflor Lopez Gonzalez McDowell Miller Ng Odinson Reed
|
73
|
+
Roberts Rodriguez Sanders Schmidt Scott Smith Stewart Taylor Tesla Torres Turner
|
74
|
+
Walker Ward Warner White Williams Wilson Wong Young Zeta Zimmerman
|
75
|
+
]
|
76
|
+
|
77
|
+
"#{first_names.sample} #{last_names.sample}"
|
78
|
+
end
|
79
|
+
|
80
|
+
# Gives back the TestUser for the bot
|
81
|
+
# @return [TestUser] Waylon's User instance
|
82
|
+
def self.whoami
|
83
|
+
find_by_email("waylon.smithers@example.com")
|
84
|
+
end
|
85
|
+
|
86
|
+
# @param user_id [Integer] The ID of the user in the TestSense's user list
|
87
|
+
# @param details [Hash] Optional User details (can be looked up later)
|
88
|
+
def initialize(user_id, details = {})
|
89
|
+
@id = user_id.to_i
|
90
|
+
@details = details
|
91
|
+
end
|
92
|
+
|
93
|
+
# The User's full name (:user from the details Hash)
|
94
|
+
# @return [String]
|
95
|
+
def display_name
|
96
|
+
details[:name]
|
97
|
+
end
|
98
|
+
|
99
|
+
# The User's email address
|
100
|
+
# @return [String]
|
101
|
+
def email
|
102
|
+
details[:email]
|
103
|
+
end
|
104
|
+
|
105
|
+
# Sends a direct TestMessage to a User
|
106
|
+
# @param content [String] The message content to send
|
107
|
+
# @return [TestMessage]
|
108
|
+
def private_message(content)
|
109
|
+
msg = {
|
110
|
+
user_id: self.class.whoami.id,
|
111
|
+
receiver_id: id,
|
112
|
+
text: content,
|
113
|
+
type: :private,
|
114
|
+
created_at: Time.now
|
115
|
+
}
|
116
|
+
TestSense.message_list << msg
|
117
|
+
TestMessage.new(TestSense.message_list.size - 1)
|
118
|
+
end
|
119
|
+
|
120
|
+
# The User's handle
|
121
|
+
# @return [String]
|
122
|
+
def handle
|
123
|
+
details[:handle]
|
124
|
+
end
|
125
|
+
|
126
|
+
# The User's current status
|
127
|
+
# @return [Symbol]
|
128
|
+
def status
|
129
|
+
details[:status]
|
130
|
+
end
|
131
|
+
|
132
|
+
# Is the User valid?
|
133
|
+
# @return [Boolean]
|
134
|
+
def valid?
|
135
|
+
true
|
136
|
+
end
|
137
|
+
|
138
|
+
# Lazily provides the details for a TestUser
|
139
|
+
# @api private
|
140
|
+
# @return [Hash] Details for this instance
|
141
|
+
def details
|
142
|
+
@details = TestSense.user_list[id].details if @details.empty?
|
143
|
+
@details.dup
|
144
|
+
end
|
145
|
+
|
146
|
+
# Creates an email address based on the name provided
|
147
|
+
# @api private
|
148
|
+
# @return [String] A generated email address
|
149
|
+
def self.email_from_name(name)
|
150
|
+
if ENV["USER_EMAIL"] && name == "homer.simpson"
|
151
|
+
ENV["USER_EMAIL"]
|
152
|
+
else
|
153
|
+
"#{name.downcase.gsub(/[\s_-]/, ".")}@example.com"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Creates a handle from a name
|
158
|
+
# @api private
|
159
|
+
# @return [String] A generated user handle
|
160
|
+
def self.handle_from_name(name)
|
161
|
+
name.downcase.split(/[\s_-]/).first
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Waylon
|
4
|
+
module RSpec
|
5
|
+
# A TestWorker to run queued Skills
|
6
|
+
class TestWorker
|
7
|
+
# Instructs the worker to grab an item off the Queue and run it
|
8
|
+
# @param queue [Queue] The queue that contains work to be done
|
9
|
+
def self.handle(queue)
|
10
|
+
skill, action, details = queue.pop
|
11
|
+
skill.perform(action, details)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/waylon/rspec.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rspec"
|
4
|
+
require "rspec/expectations"
|
5
|
+
require "rspec/mocks"
|
6
|
+
|
7
|
+
major, *_unused = RSpec::Core::Version::STRING.split(/\./)
|
8
|
+
abort "RSpec 3 or greater required" if major.to_i < 3
|
9
|
+
|
10
|
+
require "moneta"
|
11
|
+
require "set"
|
12
|
+
|
13
|
+
require "waylon/core"
|
14
|
+
require "waylon/skills/default"
|
15
|
+
require "waylon/rspec/skill"
|
16
|
+
require "waylon/rspec/test_channel"
|
17
|
+
require "waylon/rspec/test_message"
|
18
|
+
require "waylon/rspec/test_sense"
|
19
|
+
require "waylon/rspec/test_user"
|
20
|
+
require "waylon/rspec/test_worker"
|
21
|
+
|
22
|
+
module Waylon
|
23
|
+
# RSpec stuff that allows specialized Waylon testing
|
24
|
+
module RSpec
|
25
|
+
class << self
|
26
|
+
# @param base [Object] The class including the module.
|
27
|
+
# @return [void]
|
28
|
+
def included(base)
|
29
|
+
base.class_eval do
|
30
|
+
before do
|
31
|
+
config = Waylon::Config.instance
|
32
|
+
config.load_env
|
33
|
+
Waylon::Cache.clear
|
34
|
+
Waylon::Storage.clear
|
35
|
+
|
36
|
+
Waylon::RSpec::TestChannel.find_or_create("random")
|
37
|
+
Waylon::RSpec::TestUser.find_or_create(
|
38
|
+
name: "Waylon Smithers",
|
39
|
+
email: "waylon.smithers@example.com"
|
40
|
+
)
|
41
|
+
Waylon::RSpec::TestUser.find_or_create(name: "Homer Simpson")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
Waylon::Cache = Moneta.new(:Cookie)
|
50
|
+
Waylon::Storage = Moneta.new(:Cookie)
|
data/lib/waylon/sense.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Waylon
|
4
|
+
# Base class for Senses (Usually messaging providers like Slack)
|
5
|
+
class Sense
|
6
|
+
include BaseComponent
|
7
|
+
|
8
|
+
# Almost always meant to be overridden, this is how the Sense wraps text in a code block
|
9
|
+
# @param text [String] The string to codify
|
10
|
+
# @return [String] Properly wrapped content
|
11
|
+
def self.codify(text)
|
12
|
+
"```\n#{text}```"
|
13
|
+
end
|
14
|
+
|
15
|
+
# Config namespace for config keys
|
16
|
+
# @return [String] The namespace for config keys
|
17
|
+
def self.config_namespace
|
18
|
+
"senses.#{component_namespace}"
|
19
|
+
end
|
20
|
+
|
21
|
+
# The connection between Senses and Skills happens here, via a Route and a Hash of details
|
22
|
+
# @param route [Route] route The matching Route from the SkillRegistry
|
23
|
+
# @param request_id [String] The ID (from the messaging platform) of the request
|
24
|
+
# @param body [String] Message content for the Skill
|
25
|
+
# @api private
|
26
|
+
def self.enqueue(route, request_id, body)
|
27
|
+
details = {
|
28
|
+
"sense" => self,
|
29
|
+
"message" => request_id,
|
30
|
+
"tokens" => route.tokens(body.strip)
|
31
|
+
}
|
32
|
+
|
33
|
+
Resque.enqueue route.destination, route.action, details
|
34
|
+
end
|
35
|
+
|
36
|
+
# Provides a simple mechanism for referencing the Group subclass provided by this Sense
|
37
|
+
# @return [Class] A Group subclass
|
38
|
+
def self.group_class
|
39
|
+
Group
|
40
|
+
end
|
41
|
+
|
42
|
+
# "At-mention" a User via the Sense. This is usually overridden on Sense subclasses.
|
43
|
+
# @param user [Waylon::User] The User to mention
|
44
|
+
# @return [String]
|
45
|
+
def self.mention(user)
|
46
|
+
"@#{user.handle}"
|
47
|
+
end
|
48
|
+
|
49
|
+
# Provides a simple mechanism for referencing the Message subclass provided by this Sense
|
50
|
+
# @return [Class] A Message subclass
|
51
|
+
def self.message_class
|
52
|
+
Message
|
53
|
+
end
|
54
|
+
|
55
|
+
# Called by Resque to actually use this BaseComponent. Hands off to run() method
|
56
|
+
# @param content [Hash] The payload hash for use in processing the message
|
57
|
+
def self.perform(content)
|
58
|
+
run(content)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Redis/Resque queue name
|
62
|
+
# @api private
|
63
|
+
# @return [Symbol]
|
64
|
+
def self.queue
|
65
|
+
:senses
|
66
|
+
end
|
67
|
+
|
68
|
+
# Provides a simple mechanism for referencing the User subclass provided by this Sense
|
69
|
+
# @return [Class] A User subclass
|
70
|
+
def self.user_class
|
71
|
+
User
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Waylon
|
4
|
+
# Registry of Sense subclasses known to Waylon
|
5
|
+
class SenseRegistry
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
# A convenience wrapper around the singleton instance #register method
|
9
|
+
# @param (see #register)
|
10
|
+
# @return (see #register)
|
11
|
+
def self.register(name, class_name)
|
12
|
+
instance.register(name, class_name)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Add the provided Sense class to the registry under `name`
|
16
|
+
# @param name [String] The name of the Sense in the registry
|
17
|
+
# @param class_name [Class] The Sense subclass to add
|
18
|
+
# @return [Class] The Sense subclass
|
19
|
+
def register(name, class_name)
|
20
|
+
@senses ||= {}
|
21
|
+
@senses[name.to_s] = class_name
|
22
|
+
end
|
23
|
+
|
24
|
+
# Provides a Hash version of this registry
|
25
|
+
# @return [Hash]
|
26
|
+
def to_hash
|
27
|
+
(@senses || {}).dup
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|