awesome_bot_factory 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Rakefile +23 -0
- data/awesome_bot_factory.gemspec +25 -0
- data/bin/awesomebotfactory +111 -0
- data/lib/awesome_bot_factory.rb +9 -0
- data/lib/awesome_bot_factory/example_payload.rb +32 -0
- data/lib/awesome_bot_factory/skill.rb +41 -0
- data/lib/awesome_bot_factory/version.rb +3 -0
- data/lib/templates/Gemfile.erb +13 -0
- data/lib/templates/Procfile.erb +2 -0
- data/lib/templates/config.ru.erb +8 -0
- data/lib/templates/readme.md.erb +4 -0
- data/lib/templates/skill.rb.erb +18 -0
- data/lib/templates/skill_spec.rb.erb +22 -0
- data/lib/templates/spec_helper.rb.erb +14 -0
- data/spec/awesome_bot_factory/skill_spec.rb +63 -0
- data/spec/spec_helper.rb +8 -0
- metadata +117 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler.setup
|
3
|
+
require "rspec"
|
4
|
+
require "rspec/core/rake_task"
|
5
|
+
|
6
|
+
task :gem => :build
|
7
|
+
task :build do
|
8
|
+
system "gem build awesome_bot_factory.gemspec"
|
9
|
+
end
|
10
|
+
|
11
|
+
task :install => :build do
|
12
|
+
system "gem install awesome_bot_factory-#{AwesomeBotFactory::VERSION}.gem"
|
13
|
+
end
|
14
|
+
|
15
|
+
task :release => :build do
|
16
|
+
system "git tag -a #{AwesomeBotFactory::VERSION} -m 'Tagging #{AwesomeBotFactory::VERSION}'"
|
17
|
+
system "git push --tags"
|
18
|
+
system "gem push awesome_bot_factory-#{AwesomeBotFactory::VERSION}.gem"
|
19
|
+
end
|
20
|
+
|
21
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
22
|
+
spec.pattern = "spec/**/*_spec.rb"
|
23
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
$: << File.expand_path('../lib', __FILE__)
|
3
|
+
require "awesome_bot_factory/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "awesome_bot_factory"
|
7
|
+
s.version = AwesomeBotFactory::VERSION
|
8
|
+
s.authors = ["Michael Bumann"]
|
9
|
+
s.email = ["michael@railslove.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{The AwesomeBotFactory gem}
|
12
|
+
s.description = %q{The AwesomeBotFactory lets you generate lovely bots for your campfire rooms. This gem brings a simple skeleton to build your bots}
|
13
|
+
|
14
|
+
s.rubyforge_project = "[none]"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_path = "lib"
|
20
|
+
|
21
|
+
s.add_dependency 'activesupport', '~> 3.0.0'
|
22
|
+
s.add_dependency 'thor', '~> 0.14.6'
|
23
|
+
s.add_development_dependency "rake"
|
24
|
+
s.add_development_dependency "rspec"
|
25
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
require "thor"
|
5
|
+
require 'active_support/inflector'
|
6
|
+
require 'awesome_bot_factory'
|
7
|
+
class AwsomeBotFactoryRunner < Thor
|
8
|
+
include Thor::Actions
|
9
|
+
source_root File.expand_path("../lib/templates", File.dirname(__FILE__))
|
10
|
+
|
11
|
+
desc "deploy", "deploys the application to heroku"
|
12
|
+
def deploy(name=nil)
|
13
|
+
@skill_app_name = name || "abf-#{Dir.getwd.split("/").last}"
|
14
|
+
if !bot_directory?
|
15
|
+
say "no bot found. please make sure to run this command in your application directory", :red
|
16
|
+
return
|
17
|
+
end
|
18
|
+
|
19
|
+
run "bundle install", :capture => true
|
20
|
+
|
21
|
+
git_dirty = !(run("git status", :capture => true) =~ /nothing to commit/)
|
22
|
+
if git_dirty
|
23
|
+
run "git add .", :capture => true
|
24
|
+
run "git commit -a -m 'deployment'", :capture => true
|
25
|
+
end
|
26
|
+
|
27
|
+
remotes = run "git remote", :capture => true
|
28
|
+
heroku_initialized = remotes =~ /heroku/
|
29
|
+
if !heroku_initialized
|
30
|
+
say "creating a new heroku application", :green
|
31
|
+
run "heroku apps:create #{skill_app_name} --stack cedar"
|
32
|
+
end
|
33
|
+
run "git push heroku master"
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "create name", "Creates a new application skeleton for a new awesome bot factory skill"
|
37
|
+
def create(name)
|
38
|
+
directory = bot_directory? ? "." : name.underscore
|
39
|
+
say "Welcome to the AWESOME BOT FACTORY!", :green
|
40
|
+
say "setting up your laboratory for #{name}", :green
|
41
|
+
@skill_class_name = name.underscore.camelize
|
42
|
+
@skill_file_name = name.underscore
|
43
|
+
template('skill.rb.erb', "#{directory}/#{skill_file_name}.rb")
|
44
|
+
if bot_directory?
|
45
|
+
insert_into_file("#{directory}/config.ru", "\nrequire File.join(File.dirname(__FILE__), '#{skill_file_name}.rb')", :after => "Bundler.require")
|
46
|
+
append_file("#{directory}/config.ru", %Q{
|
47
|
+
map '/#{skill_file_name}' do
|
48
|
+
run #{skill_class_name}
|
49
|
+
end
|
50
|
+
});
|
51
|
+
inside(directory) do
|
52
|
+
run "git commit -a -m 'hello #{name}'"
|
53
|
+
end
|
54
|
+
else
|
55
|
+
template('config.ru.erb', "#{directory}/config.ru")
|
56
|
+
template('Procfile.erb', "#{directory}/Procfile")
|
57
|
+
template('Gemfile.erb', "#{directory}/Gemfile")
|
58
|
+
template('readme.md.erb', "#{directory}/README.md")
|
59
|
+
template('skill_spec.rb.erb', "#{directory}/spec/#{skill_file_name}_spec.rb")
|
60
|
+
template('spec_helper.rb.erb', "#{directory}/spec/spec_helper.rb")
|
61
|
+
inside(directory) do
|
62
|
+
run 'bundle install', :capture => true
|
63
|
+
run 'git init'
|
64
|
+
run 'git add .'
|
65
|
+
run "git commit -a -m 'hello #{name}'"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
say "#{skill_author}, #{name} was created, happy hacking!", :green
|
69
|
+
end
|
70
|
+
|
71
|
+
desc "request", "send a test payload to your local application and see what it returns"
|
72
|
+
def request(name, matches="")
|
73
|
+
require "net/http"
|
74
|
+
require "awesome_bot_factory"
|
75
|
+
require "json"
|
76
|
+
|
77
|
+
attributes = JSON.parse(matches) rescue {"match" => matches.split(",")}
|
78
|
+
name = name.underscore
|
79
|
+
|
80
|
+
url = URI.parse("http://localhost:9292/#{name}")
|
81
|
+
request = Net::HTTP::Post.new(url.path)
|
82
|
+
request.body = AwesomeBotFactory::ExamplePayload.new(attributes).as_json
|
83
|
+
response = Net::HTTP.new(url.host, url.port).start {|http| http.request(request) }
|
84
|
+
|
85
|
+
say response.body
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def skill_file_name
|
91
|
+
@skill_file_name
|
92
|
+
end
|
93
|
+
|
94
|
+
def skill_class_name
|
95
|
+
@skill_class_name
|
96
|
+
end
|
97
|
+
|
98
|
+
def skill_author
|
99
|
+
@skill_author ||= ENV["USER"]
|
100
|
+
end
|
101
|
+
|
102
|
+
def skill_app_name
|
103
|
+
(@skill_app_name || @skill_file_name).to_s.gsub("_","-").downcase
|
104
|
+
end
|
105
|
+
|
106
|
+
def bot_directory?
|
107
|
+
!Dir["*.rb"].find{|f| File.read(f) =~ /AwesomeBotFactory::Skill/ }.nil?
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
AwsomeBotFactoryRunner.start
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require "json"
|
2
|
+
require "awesome_bot_factory/version"
|
3
|
+
require 'active_support/configurable'
|
4
|
+
require 'active_support/core_ext/hash/keys'
|
5
|
+
|
6
|
+
module AwesomeBotFactory
|
7
|
+
autoload :Skill, 'awesome_bot_factory/skill'
|
8
|
+
autoload :ExamplePayload, 'awesome_bot_factory/example_payload'
|
9
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module AwesomeBotFactory
|
2
|
+
class ExamplePayload < Hash
|
3
|
+
DEFAULTS = {
|
4
|
+
"body" => "campfire message",
|
5
|
+
"room_id" => "123456",
|
6
|
+
"user_id" => "7890",
|
7
|
+
"user" => {
|
8
|
+
"name" => "Choco Bit",
|
9
|
+
"avatar_url" => "http://asset0.37img.com/global/.../avatar.png",
|
10
|
+
"type" => "Member",
|
11
|
+
"admin" => false
|
12
|
+
},
|
13
|
+
"user_name" => "choco",
|
14
|
+
"match" => ["key:command value", "command", "value"],
|
15
|
+
"slug" => "hexhash"
|
16
|
+
}
|
17
|
+
|
18
|
+
def initialize(attributes={})
|
19
|
+
self.merge!(DEFAULTS)
|
20
|
+
self.merge!(attributes)
|
21
|
+
end
|
22
|
+
|
23
|
+
def as_hash
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def as_json
|
28
|
+
as_hash.to_json
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module AwesomeBotFactory
|
2
|
+
class Skill
|
3
|
+
include ActiveSupport::Configurable
|
4
|
+
|
5
|
+
attr_accessor :message
|
6
|
+
|
7
|
+
def self.matches(*matches)
|
8
|
+
@@matches = matches
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.call(env)
|
12
|
+
message = JSON.parse(env["rack.input"].read)
|
13
|
+
[200, {"Content-Type" => "application/json"}, [ self.new(message).reply.to_json] ]
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(campfire_message={})
|
17
|
+
self.message = campfire_message
|
18
|
+
self.message["match"] ||= [] # let's just make sure we have an hash there and don't break if it is in any case empty
|
19
|
+
end
|
20
|
+
|
21
|
+
def read_attribute(attr)
|
22
|
+
index = @@matches.index(attr.to_sym)
|
23
|
+
if index
|
24
|
+
self.message["match"][index+1]
|
25
|
+
else
|
26
|
+
self.message[attr.to_s] || self.message[attr.to_sym]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def attribute_defined?(attr)
|
31
|
+
!@@matches.index(attr).nil?
|
32
|
+
end
|
33
|
+
|
34
|
+
def method_missing(name, *args)
|
35
|
+
return self.message[name.to_s] if self.message.key?(name.to_s)
|
36
|
+
return self.message[name.to_sym] if self.message.key?(name.to_sym)
|
37
|
+
return self.read_attribute(name) if self.attribute_defined?(name)
|
38
|
+
super(name,*args)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
gem "thin"
|
4
|
+
gem "rack"
|
5
|
+
gem "awesome_bot_factory", '~><%= AwesomeBotFactory::VERSION %>'
|
6
|
+
|
7
|
+
group :development do
|
8
|
+
gem "heroku"
|
9
|
+
end
|
10
|
+
group :test do
|
11
|
+
gem "rspec", '~>2.6.0'
|
12
|
+
gem "rack-test", '~>0.6.1', :require => "rack/test"
|
13
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class <%= skill_class_name %> < AwesomeBotFactory::Skill
|
2
|
+
|
3
|
+
configure do |c|
|
4
|
+
c.name = "<%= skill_class_name %>"
|
5
|
+
c.description = ""
|
6
|
+
c.author = "<%= skill_author %>"
|
7
|
+
c.url = "http://abf-<%= skill_app_name %>.herokuapp.com/<%= skill_file_name %>"
|
8
|
+
c.regex = "<%= skill_file_name %>:(.+)"
|
9
|
+
c.help = ""
|
10
|
+
end
|
11
|
+
|
12
|
+
matches :matches, :of, :your, :regex
|
13
|
+
|
14
|
+
def reply
|
15
|
+
{ :type => "TextMessage", :body => "hello world" }
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe <%= skill_class_name %> do
|
4
|
+
|
5
|
+
subject { <%= skill_class_name %>.new(AwesomeBotFactory::ExamplePayload.new("match" => ["bot:dance around the world", "dance", "around", "the", "world"])) }
|
6
|
+
|
7
|
+
it { should have_matches_for(:matches, :of, :your, :regex) }
|
8
|
+
its(:matches) { should eql("dance") }
|
9
|
+
its(:reply) { should eql({ :type => "TextMessage", :body => "hello world" })}
|
10
|
+
|
11
|
+
describe "as a rack application" do
|
12
|
+
let(:app) { <%= skill_class_name %> }
|
13
|
+
before do
|
14
|
+
post "/<%= skill_file_name %>", {}, :input => AwesomeBotFactory::ExamplePayload.new("match" => ["bot:dance around the world", "dance", "around", "the", "world"]).as_json
|
15
|
+
end
|
16
|
+
let(:json_body) { JSON.parse(last_response.body) }
|
17
|
+
|
18
|
+
it { last_response.should be_ok }
|
19
|
+
it { json_body["type"].should eql("TextMessage") }
|
20
|
+
it { json_body["body"].should eql("hello world") }
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "bundler/setup"
|
2
|
+
Bundler.require(:default, :test)
|
3
|
+
|
4
|
+
require File.expand_path("../<%= skill_file_name %>.rb", File.dirname(__FILE__))
|
5
|
+
|
6
|
+
RSpec.configure do |config|
|
7
|
+
config.include Rack::Test::Methods
|
8
|
+
end
|
9
|
+
|
10
|
+
RSpec::Matchers.define :have_matches_for do |*expected|
|
11
|
+
match do |actual|
|
12
|
+
expected.all? { |e| actual.class.class_variable_get("@@matches").include?(e) }
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
describe AwesomeBotFactory::Skill do
|
3
|
+
|
4
|
+
it "should initialize an empty match array if nothing given" do
|
5
|
+
TestSkill.new.match.should eql([])
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "Rack endpoint" do
|
9
|
+
|
10
|
+
before do
|
11
|
+
JSON.should_receive(:parse).with("body").and_return({:all=>:fake})
|
12
|
+
TestSkill.should_receive(:new).with({:all => :fake}).and_return(mock(:reply => {:body => "response"}))
|
13
|
+
end
|
14
|
+
subject {
|
15
|
+
TestSkill.call({"rack.input" => StringIO.new("body")})
|
16
|
+
}
|
17
|
+
|
18
|
+
its([0]) { should eql(200) }
|
19
|
+
its([1]) { should eql({"Content-Type" => "application/json"}) }
|
20
|
+
its([2]) { should eql(['{"body":"response"}']) }
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "Attributes" do
|
25
|
+
before do
|
26
|
+
TestSkill.matches :one, :two
|
27
|
+
end
|
28
|
+
|
29
|
+
subject { TestSkill.new({"user" => "Mole", :room_id => "12345", :nill => nil, "match" => [1,2,3]}) }
|
30
|
+
|
31
|
+
describe "accessors" do
|
32
|
+
|
33
|
+
its(:one) { should eql(2)}
|
34
|
+
its(:two) { should eql(3)}
|
35
|
+
its(:user) { should eql("Mole") }
|
36
|
+
its(:room_id) { should eql("12345") }
|
37
|
+
its(:nill) { should be_nil }
|
38
|
+
its(:message) { should eql({"user" => "Mole", :room_id => "12345", :nill => nil, "match" => [1,2,3]})}
|
39
|
+
specify { expect { subject.undefined_methdo }.to raise_error(NoMethodError) }
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "read attributes" do
|
43
|
+
it { subject.read_attribute(:user).should eql("Mole") }
|
44
|
+
it { subject.read_attribute(:room_id).should eql("12345") }
|
45
|
+
it { subject.read_attribute(:nill).should be_nil }
|
46
|
+
it { subject.read_attribute(:not_found).should be_nil }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "Configuration" do
|
51
|
+
before(:all) do
|
52
|
+
TestSkill.configure do |c|
|
53
|
+
c.name = "Bender"
|
54
|
+
c.description = "THE Bot"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
subject { TestSkill.config }
|
59
|
+
its(:name) { should eql("Bender") }
|
60
|
+
its(:description) { should eql("THE Bot") }
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: awesome_bot_factory
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Michael Bumann
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-09-07 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activesupport
|
16
|
+
requirement: &2159771200 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.0.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2159771200
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: thor
|
27
|
+
requirement: &2159770700 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.14.6
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *2159770700
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rake
|
38
|
+
requirement: &2159770320 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *2159770320
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rspec
|
49
|
+
requirement: &2159769860 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *2159769860
|
58
|
+
description: The AwesomeBotFactory lets you generate lovely bots for your campfire
|
59
|
+
rooms. This gem brings a simple skeleton to build your bots
|
60
|
+
email:
|
61
|
+
- michael@railslove.com
|
62
|
+
executables:
|
63
|
+
- awesomebotfactory
|
64
|
+
extensions: []
|
65
|
+
extra_rdoc_files: []
|
66
|
+
files:
|
67
|
+
- .gitignore
|
68
|
+
- Gemfile
|
69
|
+
- Rakefile
|
70
|
+
- awesome_bot_factory.gemspec
|
71
|
+
- bin/awesomebotfactory
|
72
|
+
- lib/awesome_bot_factory.rb
|
73
|
+
- lib/awesome_bot_factory/example_payload.rb
|
74
|
+
- lib/awesome_bot_factory/skill.rb
|
75
|
+
- lib/awesome_bot_factory/version.rb
|
76
|
+
- lib/templates/Gemfile.erb
|
77
|
+
- lib/templates/Procfile.erb
|
78
|
+
- lib/templates/config.ru.erb
|
79
|
+
- lib/templates/readme.md.erb
|
80
|
+
- lib/templates/skill.rb.erb
|
81
|
+
- lib/templates/skill_spec.rb.erb
|
82
|
+
- lib/templates/spec_helper.rb.erb
|
83
|
+
- spec/awesome_bot_factory/skill_spec.rb
|
84
|
+
- spec/spec_helper.rb
|
85
|
+
homepage: ''
|
86
|
+
licenses: []
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options: []
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
none: false
|
93
|
+
requirements:
|
94
|
+
- - ! '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
segments:
|
98
|
+
- 0
|
99
|
+
hash: 3025311330780698384
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
+
none: false
|
102
|
+
requirements:
|
103
|
+
- - ! '>='
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
segments:
|
107
|
+
- 0
|
108
|
+
hash: 3025311330780698384
|
109
|
+
requirements: []
|
110
|
+
rubyforge_project: ! '[none]'
|
111
|
+
rubygems_version: 1.8.5
|
112
|
+
signing_key:
|
113
|
+
specification_version: 3
|
114
|
+
summary: The AwesomeBotFactory gem
|
115
|
+
test_files:
|
116
|
+
- spec/awesome_bot_factory/skill_spec.rb
|
117
|
+
- spec/spec_helper.rb
|