cksh_commander 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 +9 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/README.md +122 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/cksh_commander.gemspec +25 -0
- data/lib/cksh_commander.rb +31 -0
- data/lib/cksh_commander/command.rb +91 -0
- data/lib/cksh_commander/data.rb +27 -0
- data/lib/cksh_commander/response.rb +19 -0
- data/lib/cksh_commander/runner.rb +27 -0
- data/lib/cksh_commander/version.rb +3 -0
- metadata +101 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 921acc643549e603aa612742d7fd73c4c6e49389
|
4
|
+
data.tar.gz: 64670007c67675f60e1cbd7359002df75d8c8c61
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 06cb6b5a144fb04bf23a189f92f7d40b88f2ab79c1d568d3927138e0e704fc07eda38030ff16eadb034c4e65bcff6e6028d8da36e5fc410253571c184af07045
|
7
|
+
data.tar.gz: 7a09443da85a65eae2f75ffa9008bb02cdd36fb39d7050af90892c50e58afd010ee5bfc36113e8df40778dedbb90fa0c85862b2dc140edd66fea63c8851498b8
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
## :stars: Sla[CK] Sla[SH] Commander
|
2
|
+
|
3
|
+
[]()
|
4
|
+
|
5
|
+
CKSHCommander is a Ruby gem that simplifies the task of processing [Slack slash
|
6
|
+
commands](https://api.slack.com/slash-commands). It provides a class-based
|
7
|
+
convention for authoring command handlers, and it supports basic subcommands and
|
8
|
+
arguments.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
You can install `cksh_commander` via RubyGems.
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
gem 'cksh_commander'
|
16
|
+
```
|
17
|
+
|
18
|
+
## Usage
|
19
|
+
|
20
|
+
Defining a custom command is easy. First, create a `commands/` directory and tell
|
21
|
+
`CKSHCommander` where it can find your commands. Let's say we've created a `commands/`
|
22
|
+
directory at the root of our project. Our configuration is as follows:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
# app.rb
|
26
|
+
|
27
|
+
require 'cksh_commander'
|
28
|
+
|
29
|
+
CKSHCommander.configure do |c|
|
30
|
+
c.commands_path = File.expand_path("../commands", __FILE__)
|
31
|
+
end
|
32
|
+
```
|
33
|
+
|
34
|
+
Now, let's assume that we've set up an `/example` Slack command. We want to create
|
35
|
+
a directory-based container for everything that our command might interact
|
36
|
+
with. (For example, we may wish to log some command-specific data in a text file.)
|
37
|
+
All that we have to do is to create a directory under our `commands/` directory
|
38
|
+
that matches the name of the slash command. In this case:
|
39
|
+
|
40
|
+
```
|
41
|
+
root/
|
42
|
+
app.rb
|
43
|
+
commands/
|
44
|
+
example/
|
45
|
+
command.rb
|
46
|
+
...
|
47
|
+
```
|
48
|
+
|
49
|
+
The last step is to create our custom command class. We name this class `Command` to
|
50
|
+
abide by the convention. We define our class as follows. Note that the class must inherit from
|
51
|
+
`CKSHCommander::Command`, and it should reside within a module that shares
|
52
|
+
its name with the command.
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
# commands/example/command.rb
|
56
|
+
|
57
|
+
require "cksh_commander"
|
58
|
+
|
59
|
+
module Example
|
60
|
+
class Command < CKSHCommander::Command
|
61
|
+
set token: "gIkuvaNzQIHg97ATvDxqgjtO"
|
62
|
+
|
63
|
+
# Subcommand with no arguments
|
64
|
+
# SLACK: /example test0
|
65
|
+
def test0
|
66
|
+
set_response_text("Subcommand: test0")
|
67
|
+
end
|
68
|
+
|
69
|
+
# Subcommand with one argument
|
70
|
+
# SLACK: /example test1 text
|
71
|
+
def test1(text)
|
72
|
+
set_response_text("Subcommand: test1; Text: #{text}")
|
73
|
+
end
|
74
|
+
|
75
|
+
# No subcommand with one argument
|
76
|
+
# SLACK: /example text
|
77
|
+
def ___(text)
|
78
|
+
set_response_text("Root command; Text: #{text}")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
84
|
+
We set our slash command authentication token at the class level, and define
|
85
|
+
methods for processing subcommands. Attachments (which take the form of a hash)
|
86
|
+
can be added using `add_response_attachment(attachment)`. You can also set the
|
87
|
+
response to `'in_channel'` at the method level with `respond_in_channel!`. See
|
88
|
+
`CKSHCommander::Command` for the full implementation.
|
89
|
+
|
90
|
+
To run a command, we use `CKSHCommander::Runner`. In the example below, we've
|
91
|
+
created a simple Sinatra app to illustrate its usage with the standard Slack
|
92
|
+
slash command payload.
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
# app.rb
|
96
|
+
|
97
|
+
require "sinatra"
|
98
|
+
require "json"
|
99
|
+
require "cksh_commander"
|
100
|
+
|
101
|
+
CKSHCommander.configure do |c|
|
102
|
+
c.commands_path = File.expand_path("../commands", __FILE__)
|
103
|
+
end
|
104
|
+
|
105
|
+
post "/" do
|
106
|
+
content_type :json
|
107
|
+
|
108
|
+
command = params["command"][1..-1]
|
109
|
+
response = CKSHCommander::Runner.run(command, params)
|
110
|
+
JSON.dump(response.serialize)
|
111
|
+
end
|
112
|
+
```
|
113
|
+
|
114
|
+
## Development
|
115
|
+
|
116
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
117
|
+
|
118
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
119
|
+
|
120
|
+
## Contributing
|
121
|
+
|
122
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/openarcllc/cksh_commander.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "cksh_commander"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'cksh_commander/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "cksh_commander"
|
8
|
+
spec.version = CKSHCommander::VERSION
|
9
|
+
spec.authors = ["Travis Loncar"]
|
10
|
+
spec.email = ["travis@openarc.net"]
|
11
|
+
spec.license = 'MIT'
|
12
|
+
|
13
|
+
spec.summary = %q{Process Slack slash commands with ease.}
|
14
|
+
spec.description = %q{Process Slack slash commands with ease.}
|
15
|
+
spec.homepage = "https://github.com/openarcllc/cksh_commander"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
spec.add_development_dependency "rspec"
|
25
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "cksh_commander/command"
|
2
|
+
require "cksh_commander/data"
|
3
|
+
require "cksh_commander/response"
|
4
|
+
require "cksh_commander/runner"
|
5
|
+
require "cksh_commander/version"
|
6
|
+
|
7
|
+
module CKSHCommander
|
8
|
+
class << self
|
9
|
+
attr_accessor :config
|
10
|
+
|
11
|
+
def config
|
12
|
+
@config ||= Configuration.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def configure
|
16
|
+
yield config
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Configuration
|
21
|
+
attr_accessor :commands_path
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@commands_path = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def commands_path
|
28
|
+
@commands_path || "../commands"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require "cksh_commander/response"
|
2
|
+
|
3
|
+
module CKSHCommander
|
4
|
+
class Command
|
5
|
+
class << self
|
6
|
+
attr_accessor :token
|
7
|
+
|
8
|
+
def set(opts)
|
9
|
+
opts.each do |k,v|
|
10
|
+
send("#{k}=", v) if respond_to?(k)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_accessor :data
|
16
|
+
|
17
|
+
def initialize(data = nil)
|
18
|
+
@data = data
|
19
|
+
@response = Response.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def run
|
23
|
+
# Find subcommands that match the provided text.
|
24
|
+
underscored_text = @data.text.gsub(/\s/, '_')
|
25
|
+
matched_subcommands = subcommand_methods.select { |command|
|
26
|
+
underscored_text.match(Regexp.new("\\A#{command}"))
|
27
|
+
}
|
28
|
+
|
29
|
+
# Add the "missing" subcommand if our command object
|
30
|
+
# responds to it and we have no matched subcommands.
|
31
|
+
matched_subcommands << "___" if respond_to?("___") && matched_subcommands.empty?
|
32
|
+
|
33
|
+
if matched_subcommands.empty?
|
34
|
+
return Response.new("Cannot find the provided subcommand!")
|
35
|
+
end
|
36
|
+
|
37
|
+
# Use the longest matching subcommand. Fetch the subcommand
|
38
|
+
# method and its arity. Represent the original subcommand string.
|
39
|
+
subcommand = matched_subcommands.max_by(&:size).to_s
|
40
|
+
subcommand_m = method(subcommand)
|
41
|
+
subcommand_a = subcommand_m.arity
|
42
|
+
subcommand_o = subcommand.gsub(/_/, ' ')
|
43
|
+
|
44
|
+
# Get argument string and slurp up arguments using arity. We
|
45
|
+
# assume that only trailing arguments can be space-delimited.
|
46
|
+
argstr, args = @data.text.gsub(subcommand_o, '').strip, []
|
47
|
+
subcommand_a.times do |i|
|
48
|
+
if i + 1 == subcommand_a
|
49
|
+
args << argstr
|
50
|
+
else
|
51
|
+
arg = argstr.split(' ')[0]
|
52
|
+
argstr.gsub!(Regexp.new("\\A#{arg} "), '')
|
53
|
+
args << arg
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
if args.size != subcommand_a
|
58
|
+
return Response.new("Wrong number of arguments (#{args.size} for #{subcommand_a})")
|
59
|
+
end
|
60
|
+
|
61
|
+
send(subcommand, *args)
|
62
|
+
@response
|
63
|
+
end
|
64
|
+
|
65
|
+
def authenticated?
|
66
|
+
self.class.token && self.class.token == data.token
|
67
|
+
end
|
68
|
+
|
69
|
+
def set_response_text(text)
|
70
|
+
@response.text = text
|
71
|
+
end
|
72
|
+
|
73
|
+
def add_response_attachment(attachment)
|
74
|
+
unless attachment.is_a?(Hash)
|
75
|
+
raise ArgumentError, "Attachment must be a Hash"
|
76
|
+
end
|
77
|
+
|
78
|
+
@response.attachments << attachment
|
79
|
+
end
|
80
|
+
|
81
|
+
def respond_in_channel!
|
82
|
+
@response.type = 'in_channel'
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def subcommand_methods
|
88
|
+
methods - CKSHCommander::Command.new.methods
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module CKSHCommander
|
2
|
+
class Data
|
3
|
+
def initialize(params)
|
4
|
+
command_args.each do |arg|
|
5
|
+
instance_variable_set("@#{arg}", params.fetch(arg))
|
6
|
+
self.class.send(:attr_reader, arg)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def command_args
|
13
|
+
%w[
|
14
|
+
token
|
15
|
+
team_id
|
16
|
+
team_domain
|
17
|
+
channel_id
|
18
|
+
channel_name
|
19
|
+
user_id
|
20
|
+
user_name
|
21
|
+
command
|
22
|
+
text
|
23
|
+
response_url
|
24
|
+
]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module CKSHCommander
|
2
|
+
class Response
|
3
|
+
attr_accessor :text, :type, :attachments
|
4
|
+
|
5
|
+
def initialize(text = "")
|
6
|
+
@text = text
|
7
|
+
@type = ""
|
8
|
+
@attachments = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def serialize
|
12
|
+
{
|
13
|
+
text: @text,
|
14
|
+
response_type: @type,
|
15
|
+
attachments: @attachments
|
16
|
+
}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "cksh_commander/data"
|
2
|
+
require "cksh_commander/response"
|
3
|
+
|
4
|
+
module CKSHCommander
|
5
|
+
class Runner
|
6
|
+
def self.run(command, params)
|
7
|
+
require_constants!
|
8
|
+
data = CKSHCommander::Data.new(params)
|
9
|
+
|
10
|
+
begin
|
11
|
+
name = "#{command.capitalize}::Command"
|
12
|
+
cmd = Kernel.const_get(name).new(data)
|
13
|
+
|
14
|
+
response = cmd.authenticated? ? cmd.run : Response.new("Invalid token!")
|
15
|
+
rescue NameError
|
16
|
+
response = Response.new("Command not found...")
|
17
|
+
end
|
18
|
+
|
19
|
+
response
|
20
|
+
end
|
21
|
+
|
22
|
+
private_class_method def self.require_constants!
|
23
|
+
commands_path = CKSHCommander.config.commands_path
|
24
|
+
Dir["#{commands_path}/**/*.rb"].each {|file| require file }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cksh_commander
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Travis Loncar
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-12-09 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.10'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.10'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: Process Slack slash commands with ease.
|
56
|
+
email:
|
57
|
+
- travis@openarc.net
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".rspec"
|
64
|
+
- ".travis.yml"
|
65
|
+
- Gemfile
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- bin/console
|
69
|
+
- bin/setup
|
70
|
+
- cksh_commander.gemspec
|
71
|
+
- lib/cksh_commander.rb
|
72
|
+
- lib/cksh_commander/command.rb
|
73
|
+
- lib/cksh_commander/data.rb
|
74
|
+
- lib/cksh_commander/response.rb
|
75
|
+
- lib/cksh_commander/runner.rb
|
76
|
+
- lib/cksh_commander/version.rb
|
77
|
+
homepage: https://github.com/openarcllc/cksh_commander
|
78
|
+
licenses:
|
79
|
+
- MIT
|
80
|
+
metadata: {}
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options: []
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
requirements: []
|
96
|
+
rubyforge_project:
|
97
|
+
rubygems_version: 2.4.5
|
98
|
+
signing_key:
|
99
|
+
specification_version: 4
|
100
|
+
summary: Process Slack slash commands with ease.
|
101
|
+
test_files: []
|