slack-notifier 1.5.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/lib/slack-notifier.rb +35 -60
  3. data/lib/slack-notifier/config.rb +37 -0
  4. data/lib/slack-notifier/payload_middleware.rb +21 -0
  5. data/lib/slack-notifier/payload_middleware/base.rb +35 -0
  6. data/lib/slack-notifier/payload_middleware/format_attachments.rb +37 -0
  7. data/lib/slack-notifier/payload_middleware/format_message.rb +19 -0
  8. data/lib/slack-notifier/payload_middleware/stack.rb +35 -0
  9. data/lib/slack-notifier/util/escape.rb +15 -0
  10. data/lib/slack-notifier/util/http_client.rb +53 -0
  11. data/lib/slack-notifier/util/link_formatter.rb +63 -0
  12. data/lib/slack-notifier/version.rb +2 -1
  13. data/spec/end_to_end_spec.rb +84 -0
  14. data/spec/integration/ping_integration_test.rb +12 -5
  15. data/spec/lib/slack-notifier/config_spec.rb +71 -0
  16. data/spec/lib/slack-notifier/payload_middleware/base_spec.rb +75 -0
  17. data/spec/lib/slack-notifier/payload_middleware/format_attachments_spec.rb +35 -0
  18. data/spec/lib/slack-notifier/payload_middleware/format_message_spec.rb +26 -0
  19. data/spec/lib/slack-notifier/payload_middleware/stack_spec.rb +93 -0
  20. data/spec/lib/slack-notifier/payload_middleware_spec.rb +32 -0
  21. data/spec/lib/slack-notifier/util/http_client_spec.rb +34 -0
  22. data/spec/lib/slack-notifier/{link_formatter_spec.rb → util/link_formatter_spec.rb} +30 -19
  23. data/spec/lib/slack-notifier_spec.rb +62 -128
  24. data/spec/spec_helper.rb +20 -5
  25. metadata +30 -9
  26. data/lib/slack-notifier/default_http_client.rb +0 -51
  27. data/lib/slack-notifier/link_formatter.rb +0 -62
  28. data/spec/lib/slack-notifier/default_http_client_spec.rb +0 -37
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e74413c9d125dd2a973d6035bdc86e6f28549839
4
- data.tar.gz: 6081e63972a12dc07acc866e13f8e6bae8a1992d
3
+ metadata.gz: 47af99d5a1eeca46a62425be8842bc6ff89d9d51
4
+ data.tar.gz: 3d0519d10cb7a251a49b32b0d27b2e0616f08c54
5
5
  SHA512:
6
- metadata.gz: 252d1d23a8bb02cfe9ebee51c8d68436799daa59c95572dd18bc481797069ed2fa6fac96869845ffeae900fbf5d73c9df9b4e910f9190f7ca85937293e8d3e31
7
- data.tar.gz: bda7747d3bd8e34a0ca2798d50c06c4f2b6e426c2552c2d389247f6ed60ce48a82d26e7f77e7e9c179388e831163d36a237dfe139cdddec689fdb154ee3b7d1b
6
+ metadata.gz: 0d9a915eb0ebc25b3d1d4042055419cf8628404cf9282fffa41a4f4dfada1eb8aac4c02c9b7415226895a160ee326f0c82d048fa84ede13f829dac19130b2cc8
7
+ data.tar.gz: 314a2d7812c2d699f201753e15fbcd6c34c778f863123f2e70e7731d8753a338e984bbcb3dd4cd125f01509990b79fc84ac8a3b7daf5b2e70f5484c66cede356
@@ -1,81 +1,56 @@
1
- require 'net/http'
2
- require 'uri'
3
- require 'json'
1
+ # frozen_string_literal: true
2
+ require "uri"
3
+ require "json"
4
4
 
5
- require_relative 'slack-notifier/default_http_client'
6
- require_relative 'slack-notifier/link_formatter'
5
+ require_relative "slack-notifier/util/http_client"
6
+ require_relative "slack-notifier/util/link_formatter"
7
+ require_relative "slack-notifier/util/escape"
8
+ require_relative "slack-notifier/payload_middleware"
9
+ require_relative "slack-notifier/config"
7
10
 
8
11
  module Slack
9
12
  class Notifier
10
- attr_reader :endpoint, :default_payload
13
+ attr_reader :endpoint
11
14
 
12
- def initialize webhook_url, options={}
13
- @endpoint = URI.parse webhook_url
14
- @default_payload = options
15
- end
16
-
17
- def ping message, options={}
18
- if message.is_a?(Hash)
19
- message, options = nil, message
20
- end
21
-
22
- if attachments = options[:attachments] || options["attachments"]
23
- wrap_array(attachments).each do |attachment|
24
- ["text", :text].each do |key|
25
- attachment[key] = LinkFormatter.format(attachment[key]) if attachment.has_key?(key)
26
- end
27
- end
28
- end
29
-
30
- payload = default_payload.merge(options)
31
- client = payload.delete(:http_client) || http_client
32
- http_options = payload.delete(:http_options)
33
-
34
- unless message.nil?
35
- payload.merge!(text: LinkFormatter.format(message))
36
- end
15
+ def initialize webhook_url, options={}, &block
16
+ @endpoint = URI.parse webhook_url
37
17
 
38
- params = { payload: payload.to_json }
39
- params[:http_options] = http_options if http_options
18
+ config.http_client(options.delete(:http_client)) if options.key?(:http_client)
19
+ config.defaults options
20
+ config.instance_exec(&block) if block_given?
40
21
 
41
- client.post endpoint, params
22
+ middleware.set config.middleware
42
23
  end
43
24
 
44
- def http_client
45
- default_payload.fetch :http_client, DefaultHTTPClient
25
+ def config
26
+ @_config ||= Config.new
46
27
  end
47
28
 
48
- def channel
49
- default_payload[:channel]
50
- end
29
+ def ping message, options={}
30
+ if message.is_a?(Hash)
31
+ options = message
32
+ else
33
+ options[:text] = message
34
+ end
51
35
 
52
- def channel= channel
53
- default_payload[:channel] = channel
36
+ post options
54
37
  end
55
38
 
56
- def username
57
- default_payload[:username]
58
- end
39
+ def post payload={}
40
+ params = {}
41
+ client = payload.delete(:http_client) || config.http_client
42
+ payload = config.defaults.merge(payload)
59
43
 
60
- def username= username
61
- default_payload[:username] = username
62
- end
44
+ params[:http_options] = payload.delete(:http_options) if payload.key?(:http_options)
45
+ params[:payload] = middleware.call(payload).to_json
63
46
 
64
- HTML_ESCAPE_REGEXP = /[&><]/
65
- HTML_ESCAPE = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;' }
66
-
67
- def escape(text)
68
- text.gsub(HTML_ESCAPE_REGEXP, HTML_ESCAPE)
47
+ client.post endpoint, params
69
48
  end
70
49
 
71
- def wrap_array(object)
72
- if object.nil?
73
- []
74
- elsif object.respond_to?(:to_ary)
75
- object.to_ary || [object]
76
- else
77
- [object]
50
+ private
51
+
52
+ def middleware
53
+ @middleware ||= PayloadMiddleware::Stack.new(self)
78
54
  end
79
- end
80
55
  end
81
56
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+ module Slack
3
+ class Notifier
4
+ class Config
5
+ def initialize
6
+ @http_client = Util::HTTPClient
7
+ @defaults = {}
8
+ @middleware = [:format_message, :format_attachments]
9
+ end
10
+
11
+ def http_client client=nil
12
+ return @http_client if client.nil?
13
+ raise ArgumentError, "the http client must respond to ::post" unless client.respond_to?(:post)
14
+
15
+ @http_client = client
16
+ end
17
+
18
+ def defaults new_defaults=nil
19
+ return @defaults if new_defaults.nil?
20
+ raise ArgumentError, "the defaults must be a Hash" unless new_defaults.is_a?(Hash)
21
+
22
+ @defaults = new_defaults
23
+ end
24
+
25
+ def middleware *args
26
+ return @middleware if args.empty?
27
+
28
+ @middleware =
29
+ if args.length == 1 && args.first.is_a?(Array) || args.first.is_a?(Hash)
30
+ args.first
31
+ else
32
+ args
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+ module Slack
3
+ class Notifier
4
+ class PayloadMiddleware
5
+ class << self
6
+ def registry
7
+ @registry ||= {}
8
+ end
9
+
10
+ def register middleware, name
11
+ registry[name] = middleware
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ require_relative "payload_middleware/stack"
19
+ require_relative "payload_middleware/base"
20
+ require_relative "payload_middleware/format_message"
21
+ require_relative "payload_middleware/format_attachments"
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+ module Slack
3
+ class Notifier
4
+ class PayloadMiddleware
5
+ class Base
6
+ class << self
7
+ def middleware_name name
8
+ PayloadMiddleware.register self, name.to_sym
9
+ end
10
+
11
+ def options default_opts
12
+ @default_opts = default_opts
13
+ end
14
+
15
+ def default_opts
16
+ @default_opts ||= {}
17
+ end
18
+ end
19
+
20
+ attr_reader :notifier, :options
21
+
22
+ def initialize notifier, opts={}
23
+ @notifier = notifier
24
+ @options = self.class.default_opts.merge opts
25
+ end
26
+
27
+ # rubocop:disable Lint/UnusedMethodArgument
28
+ def call payload={}
29
+ raise NoMethodError, "method `call` not defined for class #{self.class}"
30
+ end
31
+ # rubocop:enable Lint/UnusedMethodArgument
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+ module Slack
3
+ class Notifier
4
+ class PayloadMiddleware
5
+ class FormatAttachments < Base
6
+ middleware_name :format_attachments
7
+
8
+ options formats: [:html, :markdown]
9
+
10
+ def call payload={}
11
+ attachments = payload.fetch(:attachments, payload["attachments"])
12
+ wrap_array(attachments).each do |attachment|
13
+ ["text", :text].each do |key|
14
+ if attachment.key?(key)
15
+ attachment[key] = Util::LinkFormatter.format(attachment[key], options)
16
+ end
17
+ end
18
+ end
19
+
20
+ payload
21
+ end
22
+
23
+ private
24
+
25
+ def wrap_array object
26
+ if object.nil?
27
+ []
28
+ elsif object.respond_to?(:to_ary)
29
+ object.to_ary || [object]
30
+ else
31
+ [object]
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+ module Slack
3
+ class Notifier
4
+ class PayloadMiddleware
5
+ class FormatMessage < Base
6
+ middleware_name :format_message
7
+
8
+ options formats: [:html, :markdown]
9
+
10
+ def call payload={}
11
+ return payload unless payload[:text]
12
+ payload[:text] = Util::LinkFormatter.format(payload[:text], options)
13
+
14
+ payload
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+ module Slack
3
+ class Notifier
4
+ class PayloadMiddleware
5
+ class Stack
6
+ attr_reader :notifier,
7
+ :stack
8
+
9
+ def initialize notifier
10
+ @notifier = notifier
11
+ @stack = []
12
+ end
13
+
14
+ def set *middlewares
15
+ middlewares =
16
+ if middlewares.length == 1 && middlewares.first.is_a?(Hash)
17
+ middlewares.first
18
+ else
19
+ middlewares.flatten
20
+ end
21
+
22
+ @stack = middlewares.map do |key, opts|
23
+ PayloadMiddleware.registry.fetch(key).new(*[notifier, opts].compact)
24
+ end
25
+ end
26
+
27
+ def call payload={}
28
+ stack.inject payload do |pld, middleware|
29
+ middleware.call(pld)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ module Slack
3
+ class Notifier
4
+ module Util
5
+ module Escape
6
+ HTML_REGEXP = /[&><]/
7
+ HTML_REPLACE = { "&" => "&amp;", ">" => "&gt;", "<" => "&lt;" }.freeze
8
+
9
+ def self.html string
10
+ string.gsub(HTML_REGEXP, HTML_REPLACE)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+
5
+ module Slack
6
+ class Notifier
7
+ module Util
8
+ class HTTPClient
9
+ class << self
10
+ def post uri, params
11
+ HTTPClient.new(uri, params).call
12
+ end
13
+ end
14
+
15
+ attr_reader :uri, :params, :http_options
16
+
17
+ def initialize uri, params
18
+ @uri = uri
19
+ @http_options = params.delete(:http_options) || {}
20
+ @params = params
21
+ end
22
+
23
+ def call
24
+ http_obj.request request_obj
25
+ end
26
+
27
+ private
28
+
29
+ def request_obj
30
+ req = Net::HTTP::Post.new uri.request_uri
31
+ req.set_form_data params
32
+
33
+ req
34
+ end
35
+
36
+ def http_obj
37
+ http = Net::HTTP.new uri.host, uri.port
38
+ http.use_ssl = (uri.scheme == "https")
39
+
40
+ http_options.each do |opt, val|
41
+ if http.respond_to? "#{opt}="
42
+ http.send "#{opt}=", val
43
+ else
44
+ warn "Net::HTTP doesn't respond to `#{opt}=`, ignoring that option"
45
+ end
46
+ end
47
+
48
+ http
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+ module Slack
3
+ class Notifier
4
+ module Util
5
+ class LinkFormatter
6
+ # http://rubular.com/r/19cNXW5qbH
7
+ HTML_PATTERN = / <a (?:.*?) href=['"](.+?)['"] (?:.*?)> (.+?) <\/a> /x
8
+
9
+ # http://rubular.com/r/guJbTK6x1f
10
+ MARKDOWN_PATTERN = /\[ ([^\[\]]*?) \] \( ((https?:\/\/.*?) | (mailto:.*?)) \) /x
11
+
12
+ class << self
13
+ def format string, opts={}
14
+ LinkFormatter.new(string, opts).formatted
15
+ end
16
+ end
17
+
18
+ attr_reader :formats
19
+
20
+ def initialize string, formats: [:html, :markdown]
21
+ @formats = formats
22
+ @orig = string.respond_to?(:scrub) ? string.scrub : string
23
+ end
24
+
25
+ # rubocop:disable Style/GuardClause
26
+ def formatted
27
+ sub_markdown_links(sub_html_links(@orig))
28
+ rescue => e
29
+ if RUBY_VERSION < "2.1" && e.message.include?("invalid byte sequence")
30
+ raise e, "#{e.message}. Consider including the 'string-scrub' gem to strip invalid characters"
31
+ else
32
+ raise e
33
+ end
34
+ end
35
+ # rubocop:enable Style/GuardClause
36
+
37
+ private
38
+
39
+ def sub_html_links string
40
+ return string unless formats.include?(:html)
41
+
42
+ string.gsub(HTML_PATTERN) do
43
+ slack_link Regexp.last_match[1], Regexp.last_match[2]
44
+ end
45
+ end
46
+
47
+ def sub_markdown_links string
48
+ return string unless formats.include?(:markdown)
49
+
50
+ string.gsub(MARKDOWN_PATTERN) do
51
+ slack_link Regexp.last_match[2], Regexp.last_match[1]
52
+ end
53
+ end
54
+
55
+ def slack_link link, text=nil
56
+ "<#{link}" \
57
+ "#{text && !text.empty? ? "|#{text}" : ''}" \
58
+ ">"
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end