slack_line 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/.github/workflows/linters.yml +33 -0
- data/.github/workflows/rspec.yml +33 -0
- data/.gitignore +7 -0
- data/.mdl_rules.rb +2 -0
- data/.mdlrc +2 -0
- data/.quiet_quality.yml +7 -0
- data/.rspec +1 -0
- data/.rubocop.yml +21 -0
- data/.standard.yml +3 -0
- data/Gemfile +3 -0
- data/README.md +123 -0
- data/bin/slack_line_message +49 -0
- data/bin/slack_line_thread +55 -0
- data/lib/slack_line/client.rb +19 -0
- data/lib/slack_line/configuration.rb +54 -0
- data/lib/slack_line/memoization.rb +24 -0
- data/lib/slack_line/message.rb +74 -0
- data/lib/slack_line/message_context.rb +30 -0
- data/lib/slack_line/section_context.rb +24 -0
- data/lib/slack_line/sent_message.rb +29 -0
- data/lib/slack_line/sent_thread.rb +18 -0
- data/lib/slack_line/thread.rb +73 -0
- data/lib/slack_line/thread_context.rb +35 -0
- data/lib/slack_line/version.rb +3 -0
- data/lib/slack_line.rb +31 -0
- data/slack_line.gemspec +48 -0
- metadata +231 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 61c279b95b271ef984af73457248e82ab75a576aa46820bd59cacd8c8f6c8042
|
|
4
|
+
data.tar.gz: 57bc942d244ef3b45d95344ff0ab5bffb4412bc840016d2bf8e55450e1600742
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 8dd4210ffac87ab836e38902287098458dc0368d785f22c4a70221cc949cef70a219c0a96ed2adb37098d634e1c98899d1b59719cf41cfd7a248fe4259b12956
|
|
7
|
+
data.tar.gz: 20e53e6fa522453b25513f4ddfb503b74a7e1f31153837d7fb1dacfe9c89db19b4e5a2b29839ed24d5ef999da6b8fb16bed94a2fd09c76825afa41a997c29af5
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: Linters
|
|
2
|
+
|
|
3
|
+
on: [push]
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
Linting:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
steps:
|
|
9
|
+
- uses: actions/checkout@v4
|
|
10
|
+
|
|
11
|
+
- name: Set up ruby
|
|
12
|
+
uses: ruby/setup-ruby@v1
|
|
13
|
+
with:
|
|
14
|
+
ruby-version: 3.3
|
|
15
|
+
|
|
16
|
+
- name: Cache gems
|
|
17
|
+
uses: actions/cache@v3
|
|
18
|
+
with: path: vendor/bundle
|
|
19
|
+
key: ${{ runner.os }}-linters-${{ hashFiles('Gemfile.lock') }}
|
|
20
|
+
restore-keys:
|
|
21
|
+
${{ runner.os }}-linters-
|
|
22
|
+
|
|
23
|
+
- name: Install gems
|
|
24
|
+
run: bundle install --jobs 4 --retry 3
|
|
25
|
+
|
|
26
|
+
- name: Run StandardRB
|
|
27
|
+
run: bundle exec standardrb
|
|
28
|
+
|
|
29
|
+
- name: Run Rubocop (complexity checks)
|
|
30
|
+
run: bundle exec rubocop --parallel
|
|
31
|
+
|
|
32
|
+
- name: Run Markdownlint
|
|
33
|
+
run: bundle exec mdl .
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: RSpec
|
|
2
|
+
|
|
3
|
+
on: [push]
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
RSpec:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
strategy:
|
|
9
|
+
fail-fast: false
|
|
10
|
+
matrix:
|
|
11
|
+
ruby-version: ['3.2', '3.3', '3.4', 'head']
|
|
12
|
+
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- name: Set up ruby
|
|
17
|
+
uses: ruby/setup-ruby@v1
|
|
18
|
+
with:
|
|
19
|
+
ruby-version: ${{ matrix.ruby-version }}
|
|
20
|
+
|
|
21
|
+
- name: Cache gems
|
|
22
|
+
uses: actions/cache@v3
|
|
23
|
+
with:
|
|
24
|
+
path: vendor/bundle
|
|
25
|
+
key: ${{ runner.os }}-rspec-${{ matrix.ruby-version }}-${{ hashFiles('Gemfile.lock') }}
|
|
26
|
+
restore-keys:
|
|
27
|
+
${{ runner.os }}-rspec-${{ matrix.ruby-version }}-
|
|
28
|
+
|
|
29
|
+
- name: Install gems
|
|
30
|
+
run: bundle install --jobs 4 --retry 3
|
|
31
|
+
|
|
32
|
+
- name: Run RSpec
|
|
33
|
+
run: SIMPLECOV=true bundle exec rspec
|
data/.gitignore
ADDED
data/.mdl_rules.rb
ADDED
data/.mdlrc
ADDED
data/.quiet_quality.yml
ADDED
data/.rspec
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
--require spec_helper
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
AllCops:
|
|
3
|
+
SuggestExtensions: false
|
|
4
|
+
DisabledByDefault: true
|
|
5
|
+
|
|
6
|
+
Metrics/AbcSize:
|
|
7
|
+
Max: 15
|
|
8
|
+
Metrics/CyclomaticComplexity:
|
|
9
|
+
Max: 8
|
|
10
|
+
Metrics/PerceivedComplexity:
|
|
11
|
+
Max: 7
|
|
12
|
+
|
|
13
|
+
Metrics/ClassLength:
|
|
14
|
+
CountComments: false
|
|
15
|
+
Max: 150
|
|
16
|
+
Metrics/MethodLength:
|
|
17
|
+
CountComments: false
|
|
18
|
+
Max: 15
|
|
19
|
+
Metrics/ParameterLists:
|
|
20
|
+
Max: 5
|
|
21
|
+
CountKeywordArgs: true
|
data/.standard.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# SlackLine
|
|
2
|
+
|
|
3
|
+
This is a ruby gem supporting easy construction, sending, editing, and
|
|
4
|
+
threading of messages and threads.
|
|
5
|
+
|
|
6
|
+
## Usage
|
|
7
|
+
|
|
8
|
+
Are you ready? Because this is going to be _so easy_.
|
|
9
|
+
|
|
10
|
+
```ruby
|
|
11
|
+
# Send a simple message directly
|
|
12
|
+
sent_message = SlackLine.message("Something happened!", to: "#general").post
|
|
13
|
+
|
|
14
|
+
# Construct a more complex message, then send it
|
|
15
|
+
msg = SlackLine.message do
|
|
16
|
+
section do
|
|
17
|
+
context "Don't worry! If this message surprises you, context @foobar"
|
|
18
|
+
text "A thing has happened"
|
|
19
|
+
text "Yeah, it happened for sure"
|
|
20
|
+
link "How bad?", problems_path(problem.id)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
text "More details.."
|
|
24
|
+
end
|
|
25
|
+
sent_message = msg.post(to: "#general")
|
|
26
|
+
|
|
27
|
+
# Send a _thread_ of messages (strings generate simple messages)
|
|
28
|
+
sent_thread = SlackLine.thread("First text", "Second text", msg, to: "#general").post
|
|
29
|
+
|
|
30
|
+
# You can also build them inline via dsl
|
|
31
|
+
sent_thread = SlackLine.thread(to: "@dm_recipient") do
|
|
32
|
+
message do
|
|
33
|
+
context "yeah"
|
|
34
|
+
text "That's right"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
message(already_built_message)
|
|
38
|
+
message "this makes a basic message directly"
|
|
39
|
+
end.post
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
And then once you've sent a message or thread, you'll have a SentMessage
|
|
43
|
+
or SentThread object (which is basically an Array of SentMessages). You
|
|
44
|
+
can call `SentMessage#update` on any of those messages to edit them
|
|
45
|
+
after the fact - this is especially useful for keeping a message in a
|
|
46
|
+
public channel updated with the state of the process it's linking to.
|
|
47
|
+
|
|
48
|
+
`update` accepts a String, a Message, or a block _defining_ a message.
|
|
49
|
+
To update a SentThread, you'll need to choose message:
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
sent_message.update "Edit: never mind, false alarm"
|
|
53
|
+
sent_thread.first.update "Problem Resolved!"
|
|
54
|
+
sent_thread.detect { |m| m =~ /Not yet safe/ }&.update do
|
|
55
|
+
text "it's safe now"
|
|
56
|
+
end
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Configuration
|
|
60
|
+
|
|
61
|
+
The only required setup is an OAuth token - each of these options can
|
|
62
|
+
be set via ENV or `SlackLine.configure`:
|
|
63
|
+
|
|
64
|
+
* `slack_token` or `SLACK_LINE_SLACK_TOKEN` (required) - this
|
|
65
|
+
allows the library to send messages and make API requests.
|
|
66
|
+
* `look_up_users` or `SLACK_LINE_LOOK_UP_USERS` (default false) - if
|
|
67
|
+
your workspace refuses to turn `@somebody` mentions into links or
|
|
68
|
+
notifications, you can set this and we'll parse them out, then use
|
|
69
|
+
the slack API to map them to user/group IDs.
|
|
70
|
+
* `bot_name` or `SLACK_LINE_BOT_NAME` - what to call the bot that's
|
|
71
|
+
posting (in slack). The default is to use its default name.
|
|
72
|
+
* `default_channel` or `SLACK_LINE_DEFAULT_CHANNEL` - a target name
|
|
73
|
+
(either a channel with the leading pound-sign, or a user's handle
|
|
74
|
+
with a leading at-sign). When not supplied, all `send` calls are
|
|
75
|
+
required to specify a target instead.
|
|
76
|
+
* `per_message_delay` or `SLACK_LINE_PER_MESSAGE_DELAY` is a float,
|
|
77
|
+
defaulting to 0.0. SlackLine will `sleep` for that duration after
|
|
78
|
+
each message is posted, to allow you to avoid hitting rate-limits
|
|
79
|
+
from posting many messages in a row.
|
|
80
|
+
* `per_thread_delay` or `SLACK_LINE_PER_THREAD_DELAY` is a float as
|
|
81
|
+
well - SlackLine will `sleep` for this duration after each _thread_
|
|
82
|
+
is posted, and after each non-thread message is posted.
|
|
83
|
+
|
|
84
|
+
You can just set those via the environment variables, but you can also
|
|
85
|
+
set them on the singleton configuration object:
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
SlackLine.configure do |config|
|
|
89
|
+
config.slack_token = ENV["SLACK_TOKEN"]
|
|
90
|
+
config.look_up_users = true
|
|
91
|
+
config.bot_name = "CI Bot"
|
|
92
|
+
config.default_channel = "#ci-flow"
|
|
93
|
+
config.per_message_delay = 0.2
|
|
94
|
+
config.per_thread_delay = 2.0
|
|
95
|
+
end
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Multiple Configurations
|
|
99
|
+
|
|
100
|
+
If you're working in a context where you need to support multiple
|
|
101
|
+
SlackLine configurations, don't worry! The singleton central config is
|
|
102
|
+
what the singleton central Client uses (that's what all of the top-level
|
|
103
|
+
SlackLine methods dispatch to), but you can construct additional clients
|
|
104
|
+
with their own configs easily:
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
Slackline.configure do |config|
|
|
108
|
+
config.slack_token = ENV["TOKEN"]
|
|
109
|
+
config.default_channel = "#general"
|
|
110
|
+
config.bot_name = "FooBot"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Now SlackLine.message (et al) will use SlackLine.client,
|
|
114
|
+
# configured as above.
|
|
115
|
+
|
|
116
|
+
BAR_SLACK = SlackLine::Client.new(default_channel: "#team-bar", bot_name: "BarBot")
|
|
117
|
+
|
|
118
|
+
# And now you can call those methods on `BAR_SLACK` to use a different
|
|
119
|
+
# default channel and name
|
|
120
|
+
|
|
121
|
+
BAR_SLACK.thread("Message 1", "Message 2").post
|
|
122
|
+
BAR_SLACK.message("Message 3", to: "#bar-team-3").post
|
|
123
|
+
```
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require_relative "../lib/slack_line"
|
|
4
|
+
require "optparse"
|
|
5
|
+
require "reline"
|
|
6
|
+
|
|
7
|
+
options = {
|
|
8
|
+
post_to: nil,
|
|
9
|
+
content: nil,
|
|
10
|
+
dsl: nil
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
configuration = SlackLine::Configuration.new(SlackLine.configuration)
|
|
14
|
+
|
|
15
|
+
OptionParser.new do |opts|
|
|
16
|
+
opts.banner = "Usage: build_slack_line_message [options] [content]"
|
|
17
|
+
|
|
18
|
+
opts.on("-t", "--slack-token TOKEN", "Slack API token") { |t| configuration.slack_token = t }
|
|
19
|
+
opts.on("-u", "--look-up-users", "Enable user look-up") { configuration.look_up_users = true }
|
|
20
|
+
opts.on("-n", "--bot-name NAME", "Bot name to use") { |n| configuration.bot_name = n }
|
|
21
|
+
opts.on("-p", "--post-to TARGET", "Channel or user post the message to") { |t| options[:post_to] = t }
|
|
22
|
+
end.parse!
|
|
23
|
+
|
|
24
|
+
if ARGV.empty?
|
|
25
|
+
warn "No content provided, reading (as dsl) from stdin. Control+D to finish:\n\n"
|
|
26
|
+
options[:dsl] = ""
|
|
27
|
+
while (line = Reline.readline("MSG> ", true))
|
|
28
|
+
options[:dsl] += line + "\n"
|
|
29
|
+
end
|
|
30
|
+
else
|
|
31
|
+
options[:content] = ARGV.dup
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
client = SlackLine::Client.new(configuration)
|
|
35
|
+
|
|
36
|
+
message =
|
|
37
|
+
if options[:content]
|
|
38
|
+
SlackLine::Message.new(*options[:content], client:)
|
|
39
|
+
else
|
|
40
|
+
SlackLine::Message.new(client:) { eval(options[:dsl]) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
if options[:post_to]
|
|
44
|
+
message.post(to: options[:post_to])
|
|
45
|
+
warn "Posted message to #{options[:post_to]}"
|
|
46
|
+
else
|
|
47
|
+
warn "Preview message at #{message.builder_url}\n\n"
|
|
48
|
+
puts JSON.pretty_generate(message.content.as_json)
|
|
49
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require_relative "../lib/slack_line"
|
|
4
|
+
require "optparse"
|
|
5
|
+
require "reline"
|
|
6
|
+
|
|
7
|
+
options = {
|
|
8
|
+
post_to: nil,
|
|
9
|
+
content: nil,
|
|
10
|
+
dsl: nil
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
configuration = SlackLine::Configuration.new(SlackLine.configuration)
|
|
14
|
+
|
|
15
|
+
OptionParser.new do |opts|
|
|
16
|
+
opts.banner = "Usage: build_slack_line_message [options] [content]"
|
|
17
|
+
|
|
18
|
+
opts.on("-t", "--slack-token TOKEN", "Slack API token") { |t| configuration.slack_token = t }
|
|
19
|
+
opts.on("-u", "--look-up-users", "Enable user look-up") { configuration.look_up_users = true }
|
|
20
|
+
opts.on("-n", "--bot-name NAME", "Bot name to use") { |n| configuration.bot_name = n }
|
|
21
|
+
opts.on("-p", "--post-to TARGET", "Channel or user post the message to") { |t| options[:post_to] = t }
|
|
22
|
+
end.parse!
|
|
23
|
+
|
|
24
|
+
if ARGV.empty?
|
|
25
|
+
warn "No content provided, reading (as dsl) from stdin. Control+D to finish:\n\n"
|
|
26
|
+
options[:dsl] = ""
|
|
27
|
+
while (line = Reline.readline("THD> ", true))
|
|
28
|
+
options[:dsl] += line + "\n"
|
|
29
|
+
end
|
|
30
|
+
else
|
|
31
|
+
options[:content] = ARGV.dup
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
client = SlackLine::Client.new(configuration)
|
|
35
|
+
|
|
36
|
+
thread =
|
|
37
|
+
if options[:content]
|
|
38
|
+
SlackLine::Thread.new(*options[:content], client:)
|
|
39
|
+
else
|
|
40
|
+
SlackLine::Thread.new(client:) { eval(options[:dsl]) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
if options[:post_to]
|
|
44
|
+
thread.post_to(options[:post_to])
|
|
45
|
+
warn "Posted thread to #{options[:post_to]}"
|
|
46
|
+
else
|
|
47
|
+
warn "Preview messages at:"
|
|
48
|
+
thread.builder_urls.each { |url| warn " #{url}" }
|
|
49
|
+
|
|
50
|
+
thread.each do |message|
|
|
51
|
+
puts "\n\n--------------------- Message ---------------------\n"
|
|
52
|
+
puts "Preview at: #{message.builder_url}\n\n"
|
|
53
|
+
puts JSON.pretty_generate(message.content.as_json)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module SlackLine
|
|
2
|
+
class Client
|
|
3
|
+
include Memoization
|
|
4
|
+
|
|
5
|
+
def initialize(base_config = nil, **overrides)
|
|
6
|
+
@configuration = Configuration.new(base_config, **overrides)
|
|
7
|
+
|
|
8
|
+
raise ArgumentError, "slack_token is required" if @configuration.slack_token.nil?
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
attr_reader :configuration
|
|
12
|
+
|
|
13
|
+
memoize def slack_client = Slack::Web::Client.new(token: configuration.slack_token)
|
|
14
|
+
|
|
15
|
+
def message(*text_or_blocks, &dsl_block) = Message.new(*text_or_blocks, client: self, &dsl_block)
|
|
16
|
+
|
|
17
|
+
def thread(*messages, &dsl_block) = Thread.new(*messages, client: self, &dsl_block)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module SlackLine
|
|
2
|
+
class Configuration
|
|
3
|
+
attr_accessor :slack_token,
|
|
4
|
+
:look_up_users, :bot_name, :default_channel,
|
|
5
|
+
:per_message_delay, :per_thread_delay
|
|
6
|
+
|
|
7
|
+
DEFAULTS = {
|
|
8
|
+
slack_token: nil,
|
|
9
|
+
look_up_users: false,
|
|
10
|
+
bot_name: nil,
|
|
11
|
+
default_channel: nil,
|
|
12
|
+
per_message_delay: 0.0,
|
|
13
|
+
per_thread_delay: 0.0
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
def initialize(base_config = nil, **overrides)
|
|
17
|
+
@base_config = base_config
|
|
18
|
+
@overrides = overrides
|
|
19
|
+
|
|
20
|
+
@slack_token = cascade(:slack_token, "SLACK_LINE_SLACK_TOKEN", :string)
|
|
21
|
+
@look_up_users = cascade(:look_up_users, "SLACK_LINE_LOOK_UP_USERS", :boolean)
|
|
22
|
+
@bot_name = cascade(:bot_name, "SLACK_LINE_BOT_NAME", :string)
|
|
23
|
+
@default_channel = cascade(:default_channel, "SLACK_LINE_DEFAULT_CHANNEL", :string)
|
|
24
|
+
@per_message_delay = cascade(:per_message_delay, "SLACK_LINE_PER_MESSAGE_DELAY", :float)
|
|
25
|
+
@per_thread_delay = cascade(:per_thread_delay, "SLACK_LINE_PER_THREAD_DELAY", :float)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def cascade(key, env_name, env_type)
|
|
31
|
+
if @overrides&.key?(key)
|
|
32
|
+
@overrides[key]
|
|
33
|
+
elsif @base_config
|
|
34
|
+
@base_config.public_send(key)
|
|
35
|
+
elsif ENV.key?(env_name)
|
|
36
|
+
from_env(env_name, env_type)
|
|
37
|
+
else
|
|
38
|
+
DEFAULTS[key]
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def from_env(env_name, env_type)
|
|
43
|
+
value = ENV.fetch(env_name)
|
|
44
|
+
|
|
45
|
+
if env_type == :boolean
|
|
46
|
+
%w[1 true yes].include?(value.downcase)
|
|
47
|
+
elsif env_type == :float
|
|
48
|
+
value.to_f
|
|
49
|
+
else
|
|
50
|
+
value
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module SlackLine
|
|
2
|
+
module Memoization
|
|
3
|
+
def self.included(base) = base.extend(ClassMethods)
|
|
4
|
+
|
|
5
|
+
module ClassMethods
|
|
6
|
+
def memoize(method_name)
|
|
7
|
+
original_method = instance_method(method_name)
|
|
8
|
+
|
|
9
|
+
define_method(method_name) do |*args|
|
|
10
|
+
raise(ArgumentError, "Cannot memoize methods that take arguments") if args.any?
|
|
11
|
+
|
|
12
|
+
@memoization_cache ||= {}
|
|
13
|
+
|
|
14
|
+
if @memoization_cache.key?(method_name)
|
|
15
|
+
@memoization_cache[method_name]
|
|
16
|
+
else
|
|
17
|
+
result = original_method.bind_call(self)
|
|
18
|
+
@memoization_cache[method_name] = result
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
module SlackLine
|
|
2
|
+
class Message
|
|
3
|
+
extend Forwardable
|
|
4
|
+
include Memoization
|
|
5
|
+
|
|
6
|
+
def initialize(*text_or_blocks, client:, &dsl_block)
|
|
7
|
+
@text_or_blocks = text_or_blocks
|
|
8
|
+
@dsl_block = dsl_block
|
|
9
|
+
@client = client
|
|
10
|
+
|
|
11
|
+
validate!
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
memoize def content
|
|
15
|
+
if @dsl_block
|
|
16
|
+
MessageContext.new(&@dsl_block).content
|
|
17
|
+
elsif strings?(@text_or_blocks)
|
|
18
|
+
convert_multistring(*@text_or_blocks)
|
|
19
|
+
elsif blocks?(@text_or_blocks)
|
|
20
|
+
@text_or_blocks.first
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# easier prototyping/verification. You can definitely construct illegal messages
|
|
25
|
+
# using the library in various ways, but if Slack's BlockKit Builder accepts it,
|
|
26
|
+
# it's probably right.
|
|
27
|
+
memoize def builder_url
|
|
28
|
+
blocks_json = {blocks: content_data}.to_json
|
|
29
|
+
escaped_json = CGI.escape(blocks_json)
|
|
30
|
+
"https://app.slack.com/block-kit-builder##{escaped_json}"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def post(to: nil, thread_ts: nil)
|
|
34
|
+
target = to || configuration.default_channel || raise(ConfigurationError, "No target channel specified and no default_channel configured.")
|
|
35
|
+
response = slack_client.chat_postMessage(channel: target, blocks: content_data, thread_ts:, username: configuration.bot_name)
|
|
36
|
+
SentMessage.new(content: content_data, response:, client:)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
attr_reader :client
|
|
42
|
+
def_delegators :client, :slack_client, :configuration
|
|
43
|
+
|
|
44
|
+
def validate!
|
|
45
|
+
validate_xor!
|
|
46
|
+
validate_type!
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def validate_xor!
|
|
50
|
+
raise(ArgumentError, "Provide either strings/Slack::BlockKit::Blocks, or a DSL block, not both.") if @dsl_block && @text_or_blocks.any?
|
|
51
|
+
raise(ArgumentError, "Provide either strings/Slack::BlockKit::Blocks, or a DSL block.") unless @dsl_block || @text_or_blocks.any?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def validate_type!
|
|
55
|
+
unless @text_or_blocks.empty? || blocks?(@text_or_blocks) || strings?(@text_or_blocks)
|
|
56
|
+
raise(ArgumentError, "Invalid content type: #{@text_or_blocks.class}")
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def blocks?(obj) = obj.is_a?(Array) && obj.size == 1 && obj.first.is_a?(Slack::BlockKit::Blocks)
|
|
61
|
+
|
|
62
|
+
def strings?(obj) = obj.is_a?(Array) && obj.size > 0 && obj.all? { |item| item.is_a?(String) }
|
|
63
|
+
|
|
64
|
+
def convert_multistring(*strs)
|
|
65
|
+
Slack::BlockKit.blocks do |b|
|
|
66
|
+
strs.each do |str|
|
|
67
|
+
b.section { |s| s.mrkdwn(text: str) }
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
memoize def content_data = content.as_json
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module SlackLine
|
|
2
|
+
class MessageContext
|
|
3
|
+
def initialize(&block)
|
|
4
|
+
@content = ::Slack::BlockKit.blocks do |b|
|
|
5
|
+
@in_progress_blocks = b
|
|
6
|
+
instance_exec(&block)
|
|
7
|
+
ensure
|
|
8
|
+
@in_progress_blocks = nil
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
attr_reader :content
|
|
13
|
+
|
|
14
|
+
def text(content, plain: false)
|
|
15
|
+
@in_progress_blocks.section do |s|
|
|
16
|
+
plain ? s.plain_text(text: content) : s.mrkdwn(text: content)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def section(&block) = SectionContext.new(@in_progress_blocks, &block).content
|
|
21
|
+
|
|
22
|
+
def context(content, plain: false)
|
|
23
|
+
@in_progress_blocks.context do |c|
|
|
24
|
+
plain ? c.plain_text(text: content) : c.mrkdwn(text: content)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def divider = @in_progress_blocks.divider
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module SlackLine
|
|
2
|
+
class SectionContext
|
|
3
|
+
def initialize(parent_context, &block)
|
|
4
|
+
@content = parent_context.section do |s|
|
|
5
|
+
@in_progress_section = s
|
|
6
|
+
instance_exec(&block)
|
|
7
|
+
ensure
|
|
8
|
+
@in_progress_section = nil
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
attr_reader :content
|
|
13
|
+
|
|
14
|
+
def text(content, plain: false)
|
|
15
|
+
if plain
|
|
16
|
+
@in_progress_section.plain_text(text: content)
|
|
17
|
+
else
|
|
18
|
+
@in_progress_section.mrkdwn(text: content)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def link(text, url) = @in_progress_section.button(text: text, url: url, action_id: "link-button")
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module SlackLine
|
|
2
|
+
class SentMessage
|
|
3
|
+
extend Forwardable
|
|
4
|
+
|
|
5
|
+
def initialize(response:, client:, content:, priorly: nil)
|
|
6
|
+
@content = content
|
|
7
|
+
@priorly = priorly
|
|
8
|
+
@response = response
|
|
9
|
+
@client = client
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
attr_reader :content, :priorly, :response
|
|
13
|
+
def_delegators :response, :ts, :channel
|
|
14
|
+
|
|
15
|
+
def inspect = "#<#{self.class} channel=#{channel.inspect} ts=#{ts.inspect}>"
|
|
16
|
+
|
|
17
|
+
def update(*text_or_blocks, &dsl_block)
|
|
18
|
+
updated_message = Message.new(*text_or_blocks, client:, &dsl_block)
|
|
19
|
+
new_content = updated_message.content.as_json
|
|
20
|
+
response = slack_client.chat_update(channel:, ts:, blocks: new_content)
|
|
21
|
+
SentMessage.new(content: new_content, priorly: content, response:, client:)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
attr_reader :client
|
|
27
|
+
def_delegators :client, :slack_client
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module SlackLine
|
|
2
|
+
class SentThread
|
|
3
|
+
extend Forwardable
|
|
4
|
+
include Enumerable
|
|
5
|
+
|
|
6
|
+
def initialize(*sent_messages)
|
|
7
|
+
@sent_messages = sent_messages.freeze
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
attr_reader :sent_messages
|
|
11
|
+
alias_method :messages, :sent_messages
|
|
12
|
+
def_delegators :sent_messages, :each, :map, :size, :first, :last, :empty?
|
|
13
|
+
def_delegators :first, :channel, :ts
|
|
14
|
+
alias_method :thread_ts, :ts
|
|
15
|
+
|
|
16
|
+
def inspect = "#<#{self.class} channel=#{channel.inspect} size=#{size} thread_ts=#{thread_ts.inspect}>"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
module SlackLine
|
|
2
|
+
class Thread
|
|
3
|
+
extend Forwardable
|
|
4
|
+
include Enumerable
|
|
5
|
+
include Memoization
|
|
6
|
+
|
|
7
|
+
def initialize(*supplied_messages, client:, &dsl_block)
|
|
8
|
+
@supplied_messages = supplied_messages
|
|
9
|
+
@dsl_block = dsl_block
|
|
10
|
+
@client = client
|
|
11
|
+
|
|
12
|
+
validate!
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# an Array of SlackLine::Messages
|
|
16
|
+
memoize def messages = message_contents.map { |mc| convert_supplied_message(mc) }
|
|
17
|
+
|
|
18
|
+
def_delegators :messages, :each, :[], :length, :size, :empty?
|
|
19
|
+
|
|
20
|
+
memoize def builder_urls = messages.map(&:builder_url)
|
|
21
|
+
|
|
22
|
+
def post(to: nil)
|
|
23
|
+
target = to || client.configuration.default_channel || raise(ConfigurationError, "No target channel specified and no default_channel configured.")
|
|
24
|
+
sent_messages = []
|
|
25
|
+
thread_ts = nil
|
|
26
|
+
|
|
27
|
+
messages.each do |message|
|
|
28
|
+
sent = message.post(to: target, thread_ts:)
|
|
29
|
+
thread_ts ||= sent.ts
|
|
30
|
+
sent_messages << sent
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
SentThread.new(*sent_messages)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
attr_reader :client
|
|
39
|
+
def_delegators :client, :slack_client, :configuration
|
|
40
|
+
|
|
41
|
+
def validate!
|
|
42
|
+
validate_xor!
|
|
43
|
+
validate_types!
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def validate_xor!
|
|
47
|
+
raise(ArgumentError, "Provide either texts/blocks/Messages or a DSL block, not both.") if @dsl_block && @supplied_messages.any?
|
|
48
|
+
raise(ArgumentError, "Provide either texts/blocks/Messages or a DSL block.") unless @dsl_block || @supplied_messages.any?
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def validate_types!
|
|
52
|
+
@supplied_messages.each do |sm|
|
|
53
|
+
unless sm.is_a?(String) || sm.is_a?(Slack::BlockKit::Blocks) || sm.is_a?(Message)
|
|
54
|
+
raise(ArgumentError, "Invalid message type: #{sm.class}. Excepted a String, Slack::BlockKit::Blocks, or SlackLine::Message.")
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
memoize def dsl_contents = ThreadContext.new(&@dsl_block).contents
|
|
60
|
+
|
|
61
|
+
# produces an Array of (mixed) Strings, Slack::BlockKit::Blocks, and SlackLine::Messages
|
|
62
|
+
# (ThreadContext will produce Strings and Blocks)
|
|
63
|
+
memoize def message_contents = @dsl_block ? dsl_contents : @supplied_messages
|
|
64
|
+
|
|
65
|
+
def convert_supplied_message(sm)
|
|
66
|
+
if sm.is_a?(String) || sm.is_a?(Slack::BlockKit::Blocks)
|
|
67
|
+
Message.new(sm, client:)
|
|
68
|
+
elsif sm.is_a?(Message)
|
|
69
|
+
sm
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module SlackLine
|
|
2
|
+
class ThreadContext
|
|
3
|
+
def initialize(&block)
|
|
4
|
+
@contents = []
|
|
5
|
+
instance_exec(&block)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
attr_reader :contents
|
|
9
|
+
|
|
10
|
+
def text(text)
|
|
11
|
+
fail(ArgumentError, "Text must be a String.") unless text.is_a?(String)
|
|
12
|
+
|
|
13
|
+
@contents << text
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# supplied should be a String, Slack::BlockKit::Blocks, or an already constructed SlackLine::Message
|
|
17
|
+
def message(supplied = nil, &msg_block)
|
|
18
|
+
if (supplied && msg_block) || (!supplied && !msg_block)
|
|
19
|
+
fail(ArgumentError, "Provide either a supplied message or a message block, not both.")
|
|
20
|
+
end
|
|
21
|
+
validate_supplied_message!(supplied)
|
|
22
|
+
|
|
23
|
+
@contents << (supplied || MessageContext.new(&msg_block).content)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def validate_supplied_message!(sm)
|
|
29
|
+
expected_types = [NilClass, String, Slack::BlockKit::Blocks, SlackLine::Message]
|
|
30
|
+
unless expected_types.any? { |t| sm.is_a?(t) }
|
|
31
|
+
fail(ArgumentError, "Invalid message type: #{sm.class}. Expected a String, Slack::BlockKit::Blocks, or SlackLine::Message.")
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/lib/slack_line.rb
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require "forwardable"
|
|
2
|
+
require "slack-ruby-block-kit"
|
|
3
|
+
require "slack-ruby-client"
|
|
4
|
+
require "json"
|
|
5
|
+
require "cgi"
|
|
6
|
+
|
|
7
|
+
require_relative "slack_line/memoization"
|
|
8
|
+
|
|
9
|
+
module SlackLine
|
|
10
|
+
Error = Class.new(StandardError)
|
|
11
|
+
ConfigurationError = Class.new(Error)
|
|
12
|
+
PostMessageError = Class.new(Error)
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
extend Forwardable
|
|
16
|
+
include Memoization
|
|
17
|
+
|
|
18
|
+
# The Singleton configuration object - used by the Singleton client,
|
|
19
|
+
# and as config defaults for other clients.
|
|
20
|
+
memoize def configuration = Configuration.new
|
|
21
|
+
|
|
22
|
+
def configure = yield(configuration)
|
|
23
|
+
|
|
24
|
+
memoize def client = Client.new(configuration)
|
|
25
|
+
|
|
26
|
+
def_delegators(:client, :message, :thread)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
glob = File.expand_path("../slack_line/*.rb", __FILE__)
|
|
31
|
+
Dir.glob(glob).sort.each { |f| require(f) }
|
data/slack_line.gemspec
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require_relative "lib/slack_line/version"
|
|
2
|
+
|
|
3
|
+
Gem::Specification.new do |spec|
|
|
4
|
+
spec.name = "slack_line"
|
|
5
|
+
spec.version = SlackLine::VERSION
|
|
6
|
+
spec.authors = ["Eric Mueller"]
|
|
7
|
+
spec.email = ["nevinera@gmail.com"]
|
|
8
|
+
|
|
9
|
+
spec.summary = "Build CLIs that are configured via args, file, and/or environment"
|
|
10
|
+
spec.description = <<~DESC
|
|
11
|
+
We've written code that merges/cascades default configuration, config-files,
|
|
12
|
+
environment variables, and cli-passed arguments _too many times_. This gem
|
|
13
|
+
intends to distill that into a configuration hash describing those controls
|
|
14
|
+
and relationships, so that users can supply values in multiple ways.
|
|
15
|
+
DESC
|
|
16
|
+
spec.homepage = "https://github.com/nevinera/slack_line"
|
|
17
|
+
spec.license = "MIT"
|
|
18
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 3.2.0")
|
|
19
|
+
|
|
20
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
21
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
|
22
|
+
|
|
23
|
+
spec.require_paths = ["lib"]
|
|
24
|
+
spec.bindir = "bin"
|
|
25
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
26
|
+
`git ls-files -z`
|
|
27
|
+
.split("\x0")
|
|
28
|
+
.reject { |f| f.start_with?("spec") }
|
|
29
|
+
end
|
|
30
|
+
spec.executables = Dir.chdir(File.expand_path(__dir__)) do
|
|
31
|
+
`git ls-files -z bin/`
|
|
32
|
+
.split("\x0")
|
|
33
|
+
.map { |path| path.sub(/^bin\//, "") }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
spec.add_development_dependency "rspec", "~> 3.13"
|
|
37
|
+
spec.add_development_dependency "rspec-its", "~> 1.3"
|
|
38
|
+
spec.add_development_dependency "rspec-collection_matchers", "~> 1.2.1"
|
|
39
|
+
spec.add_development_dependency "simplecov", "~> 0.22.0"
|
|
40
|
+
spec.add_development_dependency "quiet_quality"
|
|
41
|
+
spec.add_development_dependency "pry", "~> 0.14"
|
|
42
|
+
spec.add_development_dependency "standard", ">= 1.35.1"
|
|
43
|
+
spec.add_development_dependency "rubocop", ">= 1.62"
|
|
44
|
+
spec.add_development_dependency "mdl", "~> 0.12"
|
|
45
|
+
|
|
46
|
+
spec.add_dependency "slack-ruby-block-kit", "~> 0.23.0"
|
|
47
|
+
spec.add_dependency "slack-ruby-client", "~> 3.1.0"
|
|
48
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: slack_line
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Eric Mueller
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-01-31 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rspec
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '3.13'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '3.13'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rspec-its
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '1.3'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '1.3'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rspec-collection_matchers
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: 1.2.1
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: 1.2.1
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: simplecov
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: 0.22.0
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: 0.22.0
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: quiet_quality
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: pry
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0.14'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '0.14'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: standard
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: 1.35.1
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: 1.35.1
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: rubocop
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - ">="
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '1.62'
|
|
118
|
+
type: :development
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - ">="
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '1.62'
|
|
125
|
+
- !ruby/object:Gem::Dependency
|
|
126
|
+
name: mdl
|
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - "~>"
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '0.12'
|
|
132
|
+
type: :development
|
|
133
|
+
prerelease: false
|
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - "~>"
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '0.12'
|
|
139
|
+
- !ruby/object:Gem::Dependency
|
|
140
|
+
name: slack-ruby-block-kit
|
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
|
142
|
+
requirements:
|
|
143
|
+
- - "~>"
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: 0.23.0
|
|
146
|
+
type: :runtime
|
|
147
|
+
prerelease: false
|
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
149
|
+
requirements:
|
|
150
|
+
- - "~>"
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: 0.23.0
|
|
153
|
+
- !ruby/object:Gem::Dependency
|
|
154
|
+
name: slack-ruby-client
|
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
|
156
|
+
requirements:
|
|
157
|
+
- - "~>"
|
|
158
|
+
- !ruby/object:Gem::Version
|
|
159
|
+
version: 3.1.0
|
|
160
|
+
type: :runtime
|
|
161
|
+
prerelease: false
|
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
163
|
+
requirements:
|
|
164
|
+
- - "~>"
|
|
165
|
+
- !ruby/object:Gem::Version
|
|
166
|
+
version: 3.1.0
|
|
167
|
+
description: |
|
|
168
|
+
We've written code that merges/cascades default configuration, config-files,
|
|
169
|
+
environment variables, and cli-passed arguments _too many times_. This gem
|
|
170
|
+
intends to distill that into a configuration hash describing those controls
|
|
171
|
+
and relationships, so that users can supply values in multiple ways.
|
|
172
|
+
email:
|
|
173
|
+
- nevinera@gmail.com
|
|
174
|
+
executables:
|
|
175
|
+
- slack_line_message
|
|
176
|
+
- slack_line_thread
|
|
177
|
+
extensions: []
|
|
178
|
+
extra_rdoc_files: []
|
|
179
|
+
files:
|
|
180
|
+
- ".github/workflows/linters.yml"
|
|
181
|
+
- ".github/workflows/rspec.yml"
|
|
182
|
+
- ".gitignore"
|
|
183
|
+
- ".mdl_rules.rb"
|
|
184
|
+
- ".mdlrc"
|
|
185
|
+
- ".quiet_quality.yml"
|
|
186
|
+
- ".rspec"
|
|
187
|
+
- ".rubocop.yml"
|
|
188
|
+
- ".standard.yml"
|
|
189
|
+
- Gemfile
|
|
190
|
+
- README.md
|
|
191
|
+
- bin/slack_line_message
|
|
192
|
+
- bin/slack_line_thread
|
|
193
|
+
- lib/slack_line.rb
|
|
194
|
+
- lib/slack_line/client.rb
|
|
195
|
+
- lib/slack_line/configuration.rb
|
|
196
|
+
- lib/slack_line/memoization.rb
|
|
197
|
+
- lib/slack_line/message.rb
|
|
198
|
+
- lib/slack_line/message_context.rb
|
|
199
|
+
- lib/slack_line/section_context.rb
|
|
200
|
+
- lib/slack_line/sent_message.rb
|
|
201
|
+
- lib/slack_line/sent_thread.rb
|
|
202
|
+
- lib/slack_line/thread.rb
|
|
203
|
+
- lib/slack_line/thread_context.rb
|
|
204
|
+
- lib/slack_line/version.rb
|
|
205
|
+
- slack_line.gemspec
|
|
206
|
+
homepage: https://github.com/nevinera/slack_line
|
|
207
|
+
licenses:
|
|
208
|
+
- MIT
|
|
209
|
+
metadata:
|
|
210
|
+
homepage_uri: https://github.com/nevinera/slack_line
|
|
211
|
+
source_code_uri: https://github.com/nevinera/slack_line
|
|
212
|
+
post_install_message:
|
|
213
|
+
rdoc_options: []
|
|
214
|
+
require_paths:
|
|
215
|
+
- lib
|
|
216
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
217
|
+
requirements:
|
|
218
|
+
- - ">="
|
|
219
|
+
- !ruby/object:Gem::Version
|
|
220
|
+
version: 3.2.0
|
|
221
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
222
|
+
requirements:
|
|
223
|
+
- - ">="
|
|
224
|
+
- !ruby/object:Gem::Version
|
|
225
|
+
version: '0'
|
|
226
|
+
requirements: []
|
|
227
|
+
rubygems_version: 3.5.22
|
|
228
|
+
signing_key:
|
|
229
|
+
specification_version: 4
|
|
230
|
+
summary: Build CLIs that are configured via args, file, and/or environment
|
|
231
|
+
test_files: []
|