slack-notifier 1.5.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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