pechkin 1.2.2 → 1.3.1
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 +4 -4
- data/lib/pechkin.rb +1 -0
- data/lib/pechkin/app.rb +4 -114
- data/lib/pechkin/app/app.rb +43 -0
- data/lib/pechkin/app/app_builder.rb +41 -0
- data/lib/pechkin/app/app_error.rb +27 -0
- data/lib/pechkin/app/request_handler.rb +49 -0
- data/lib/pechkin/configuration.rb +4 -4
- data/lib/pechkin/connector_slack.rb +3 -2
- data/lib/pechkin/connector_telegram.rb +1 -1
- data/lib/pechkin/handler.rb +46 -24
- data/lib/pechkin/message_matcher.rb +78 -0
- data/lib/pechkin/version.rb +1 -1
- metadata +7 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 21153645eb45d32b113d2c95191e79ac5568a10829b3989ccc9f75da7b53bf9e
|
4
|
+
data.tar.gz: d80b4bbbeb7e4bdfeb251e358bb0a9c99d50eae96c5078b688bce5e12a3c73e3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 30e9654efb5c1a11c4816feb51fc494be1251e106a2fac682be9cb68738faec6e8b45c747329f90cff226ef1fe9df2c097bb0083c79d57d55b020982773dd0b8
|
7
|
+
data.tar.gz: 62d6113f5ef1aedfe36c3eb86bc619d7cbac2cd9e54a37f64cbd293b1856efe47ba572bdf25efcc2a20f7b1affbbffe8cc2e74ad9784091cc69f7fa232ebe19b
|
data/lib/pechkin.rb
CHANGED
@@ -11,6 +11,7 @@ require_relative 'pechkin/cli'
|
|
11
11
|
require_relative 'pechkin/command'
|
12
12
|
require_relative 'pechkin/exceptions'
|
13
13
|
require_relative 'pechkin/handler'
|
14
|
+
require_relative 'pechkin/message_matcher'
|
14
15
|
require_relative 'pechkin/message_template'
|
15
16
|
require_relative 'pechkin/connector'
|
16
17
|
require_relative 'pechkin/connector_slack'
|
data/lib/pechkin/app.rb
CHANGED
@@ -1,114 +1,4 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
def build(handler, options)
|
6
|
-
app = App.new
|
7
|
-
app.handler = handler
|
8
|
-
prometheus = Pechkin::PrometheusUtils.registry
|
9
|
-
logger = create_logger(options.log_dir)
|
10
|
-
|
11
|
-
Rack::Builder.app do
|
12
|
-
use Rack::CommonLogger, logger
|
13
|
-
use Rack::Deflater
|
14
|
-
use Prometheus::Middleware::Collector, registry: prometheus
|
15
|
-
# Add Auth check if found htpasswd file or it was excplicitly provided
|
16
|
-
# See CLI class for configuration details
|
17
|
-
if options.htpasswd
|
18
|
-
use Pechkin::Auth::Middleware, auth_file: options.htpasswd
|
19
|
-
end
|
20
|
-
use Prometheus::Middleware::Exporter, registry: prometheus
|
21
|
-
|
22
|
-
run app
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
private
|
27
|
-
|
28
|
-
def create_logger(log_dir)
|
29
|
-
if log_dir
|
30
|
-
raise "Directory #{log_dir} does not exist" unless File.exist?(log_dir)
|
31
|
-
|
32
|
-
log_file = File.join(log_dir, 'pechkin.log')
|
33
|
-
file = File.open(log_file, File::WRONLY | File::APPEND)
|
34
|
-
Logger.new(file)
|
35
|
-
else
|
36
|
-
Logger.new(STDOUT)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
# Rack application to handle requests
|
42
|
-
class App
|
43
|
-
attr_accessor :handler
|
44
|
-
|
45
|
-
def call(env)
|
46
|
-
RequestHandler.new(handler, env).handle
|
47
|
-
rescue StandardError => e
|
48
|
-
body = { status: 'error', reason: e.message }.to_json
|
49
|
-
['503', { 'Content-Type' => 'application/json' }, [body]]
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
# Http requests handler. We need fresh instance per each request. To keep
|
54
|
-
# internal state isolated
|
55
|
-
class RequestHandler
|
56
|
-
REQ_PATH_PATTERN = %r{^/(.+)/([^/]+)/?$}
|
57
|
-
DEFAULT_CONTENT_TYPE = { 'Content-Type' => 'application/json' }.freeze
|
58
|
-
DEFAULT_HEADERS = {}.merge(DEFAULT_CONTENT_TYPE).freeze
|
59
|
-
|
60
|
-
attr_reader :req, :env, :handler,
|
61
|
-
:channel_id, :message_id
|
62
|
-
|
63
|
-
def initialize(handler, env)
|
64
|
-
@handler = handler
|
65
|
-
@env = env
|
66
|
-
@req = Rack::Request.new(env)
|
67
|
-
|
68
|
-
@channel_id, @message_id = req.path_info.match(REQ_PATH_PATTERN) do |m|
|
69
|
-
[m[1], m[2]]
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
def handle
|
74
|
-
return not_allowed unless post?
|
75
|
-
return not_found unless message?
|
76
|
-
|
77
|
-
begin
|
78
|
-
data = JSON.parse(req.body.read)
|
79
|
-
rescue JSON::JSONError => e
|
80
|
-
return bad_request(e.message)
|
81
|
-
end
|
82
|
-
|
83
|
-
response(200, handler.handle(channel_id, message_id, data).to_json)
|
84
|
-
end
|
85
|
-
|
86
|
-
private
|
87
|
-
|
88
|
-
def message?
|
89
|
-
return false unless @channel_id && @message_id
|
90
|
-
|
91
|
-
handler.message?(@channel_id, @message_id)
|
92
|
-
end
|
93
|
-
|
94
|
-
def not_allowed
|
95
|
-
response(405, '{"status":"error", "reason":"method not allowed"}')
|
96
|
-
end
|
97
|
-
|
98
|
-
def not_found
|
99
|
-
response(404, '{"status":"error", "reason":"message not found"}')
|
100
|
-
end
|
101
|
-
|
102
|
-
def bad_request(body)
|
103
|
-
response(503, body)
|
104
|
-
end
|
105
|
-
|
106
|
-
def response(code, body)
|
107
|
-
[code.to_s, DEFAULT_HEADERS, [body]]
|
108
|
-
end
|
109
|
-
|
110
|
-
def post?
|
111
|
-
req.post?
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
1
|
+
require_relative 'app/app_builder'
|
2
|
+
require_relative 'app/app_error'
|
3
|
+
require_relative 'app/app'
|
4
|
+
require_relative 'app/request_handler'
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Pechkin
|
2
|
+
# Rack application to handle requests
|
3
|
+
class App
|
4
|
+
DEFAULT_CONTENT_TYPE = { 'Content-Type' => 'application/json' }.freeze
|
5
|
+
DEFAULT_HEADERS = {}.merge(DEFAULT_CONTENT_TYPE).freeze
|
6
|
+
|
7
|
+
attr_accessor :handler, :logger
|
8
|
+
|
9
|
+
def initialize(logger)
|
10
|
+
@logger = logger
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
req = Rack::Request.new(env)
|
15
|
+
result = RequestHandler.new(handler, req, logger).handle
|
16
|
+
response(200, result)
|
17
|
+
rescue AppError => e
|
18
|
+
proces_app_error(req, e)
|
19
|
+
rescue StandardError => e
|
20
|
+
process_unhandled_error(req, e)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def response(code, body)
|
26
|
+
[code.to_s, DEFAULT_HEADERS, [body.to_json]]
|
27
|
+
end
|
28
|
+
|
29
|
+
def proces_app_error(req, err)
|
30
|
+
data = { status: 'error', message: err.message }
|
31
|
+
req.body.rewind
|
32
|
+
body = req.body.read
|
33
|
+
logger.error "Can't process message: #{err.message}. Body: '#{body}'"
|
34
|
+
response(err.code, data)
|
35
|
+
end
|
36
|
+
|
37
|
+
def process_unhandled_error(_req, err)
|
38
|
+
data = { status: 'error', message: err.message }
|
39
|
+
logger.error("#{err.message}\n\t" + err.backtrace.join("\n\t"))
|
40
|
+
response(503, data)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Pechkin
|
2
|
+
# Application configurator and builder. This creates all needed middleware
|
3
|
+
# and stuff
|
4
|
+
class AppBuilder
|
5
|
+
def build(handler, options)
|
6
|
+
logger = create_logger(options.log_dir)
|
7
|
+
handler.logger = logger
|
8
|
+
app = App.new(logger)
|
9
|
+
app.handler = handler
|
10
|
+
prometheus = Pechkin::PrometheusUtils.registry
|
11
|
+
|
12
|
+
Rack::Builder.app do
|
13
|
+
use Rack::CommonLogger, logger
|
14
|
+
use Rack::Deflater
|
15
|
+
use Prometheus::Middleware::Collector, registry: prometheus
|
16
|
+
# Add Auth check if found htpasswd file or it was excplicitly provided
|
17
|
+
# See CLI class for configuration details
|
18
|
+
if options.htpasswd
|
19
|
+
use Pechkin::Auth::Middleware, auth_file: options.htpasswd
|
20
|
+
end
|
21
|
+
use Prometheus::Middleware::Exporter, registry: prometheus
|
22
|
+
|
23
|
+
run app
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def create_logger(log_dir)
|
30
|
+
if log_dir
|
31
|
+
raise "Directory #{log_dir} does not exist" unless File.exist?(log_dir)
|
32
|
+
|
33
|
+
log_file = File.join(log_dir, 'pechkin.log')
|
34
|
+
file = File.open(log_file, File::WRONLY | File::APPEND)
|
35
|
+
Logger.new(file)
|
36
|
+
else
|
37
|
+
Logger.new(STDOUT)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Pechkin
|
2
|
+
# Generic application error class.
|
3
|
+
#
|
4
|
+
# Allows us return meaningful error messages
|
5
|
+
class AppError < StandardError
|
6
|
+
attr_reader :code
|
7
|
+
|
8
|
+
def initialize(code, msg)
|
9
|
+
super(msg)
|
10
|
+
@code = code
|
11
|
+
end
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def bad_request(message)
|
15
|
+
AppError.new(503, message)
|
16
|
+
end
|
17
|
+
|
18
|
+
def message_not_found
|
19
|
+
AppError.new(404, 'message not found')
|
20
|
+
end
|
21
|
+
|
22
|
+
def http_method_not_allowed
|
23
|
+
AppError.new(405, 'method not allowed')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Pechkin
|
2
|
+
# Http requests handler. We need fresh instance per each request. To keep
|
3
|
+
# internal state isolated
|
4
|
+
class RequestHandler
|
5
|
+
REQ_PATH_PATTERN = %r{^/(.+)/([^/]+)/?$}
|
6
|
+
|
7
|
+
attr_reader :req, :handler,
|
8
|
+
:channel_id, :message_id,
|
9
|
+
:logger
|
10
|
+
|
11
|
+
def initialize(handler, req, logger)
|
12
|
+
@handler = handler
|
13
|
+
@req = req
|
14
|
+
@logger = logger
|
15
|
+
|
16
|
+
@channel_id, @message_id = req.path_info.match(REQ_PATH_PATTERN) do |m|
|
17
|
+
[m[1], m[2]]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def handle
|
22
|
+
raise AppError.http_method_not_allowed unless post?
|
23
|
+
raise AppError.message_not_found unless message?
|
24
|
+
|
25
|
+
data = parse_data(req.body.read)
|
26
|
+
handler.handle(channel_id, message_id, data).each do |i|
|
27
|
+
logger.info "Sent #{channel_id}/#{message_id}: #{i.to_json}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def parse_data(data)
|
34
|
+
JSON.parse(data)
|
35
|
+
rescue JSON::JSONError => e
|
36
|
+
raise AppError.bad_request(e.message)
|
37
|
+
end
|
38
|
+
|
39
|
+
def message?
|
40
|
+
return false unless @channel_id && @message_id
|
41
|
+
|
42
|
+
handler.message?(@channel_id, @message_id)
|
43
|
+
end
|
44
|
+
|
45
|
+
def post?
|
46
|
+
req.post?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -10,16 +10,16 @@ module Pechkin
|
|
10
10
|
# Pechkin reads its configuration from provided directory structure. Basic
|
11
11
|
# layout expected to be as follows:
|
12
12
|
# .
|
13
|
-
# | - bots/
|
14
|
-
# | | - marvin.yml
|
13
|
+
# | - bots/ <- Bots configuration
|
14
|
+
# | | - marvin.yml <- Each bot described by yaml file
|
15
15
|
# | | - bender.yml
|
16
16
|
# |
|
17
|
-
# | - channels/
|
17
|
+
# | - channels/ <- Channels description
|
18
18
|
# | | - slack-repository-feed
|
19
19
|
# | | - commit-hg.yml
|
20
20
|
# | | - commit-svn.yml
|
21
21
|
# |
|
22
|
-
# | - views/
|
22
|
+
# | - views/ <- Template storage
|
23
23
|
# | - commit-hg.erb
|
24
24
|
# | - commit-svn.erb
|
25
25
|
#
|
@@ -13,7 +13,8 @@ module Pechkin # :nodoc:
|
|
13
13
|
attachments = message_desc['slack_attachments'] || {}
|
14
14
|
|
15
15
|
if text.strip.empty? && attachments.empty?
|
16
|
-
return
|
16
|
+
return { channel: channel, code: 400,
|
17
|
+
response: 'Internal error: message is empty' }
|
17
18
|
end
|
18
19
|
|
19
20
|
params = { channel: channel, text: text, attachments: attachments }
|
@@ -21,7 +22,7 @@ module Pechkin # :nodoc:
|
|
21
22
|
url = 'https://slack.com/api/chat.postMessage'
|
22
23
|
response = post_data(url, params, headers: @headers)
|
23
24
|
|
24
|
-
|
25
|
+
{ channel: channel, code: response.code.to_i, response: response.body }
|
25
26
|
end
|
26
27
|
end
|
27
28
|
end
|
@@ -12,7 +12,7 @@ module Pechkin
|
|
12
12
|
params = options.update(chat_id: chat_id, text: message)
|
13
13
|
|
14
14
|
response = post_data(method_url('sendMessage'), params)
|
15
|
-
|
15
|
+
{ chat_id: chat_id, code: response.code.to_i, response: response.body }
|
16
16
|
end
|
17
17
|
|
18
18
|
private
|
data/lib/pechkin/handler.rb
CHANGED
@@ -2,10 +2,16 @@ module Pechkin
|
|
2
2
|
# Processes feeded data chunks and sends them via connectors to needed IM
|
3
3
|
# services. Can skip some requests acording to filters.
|
4
4
|
class Handler
|
5
|
-
attr_reader :channels
|
5
|
+
attr_reader :channels, :message_matcher
|
6
|
+
attr_accessor :logger
|
6
7
|
|
7
|
-
def initialize(channels)
|
8
|
+
def initialize(channels, stdout = STDOUT, stderr = STDERR)
|
8
9
|
@channels = channels
|
10
|
+
# Create empty logger by default
|
11
|
+
@logger = Logger.new(IO::NULL)
|
12
|
+
@stdout = stdout
|
13
|
+
@stderr = stderr
|
14
|
+
@message_matcher = MessageMatcher.new
|
9
15
|
end
|
10
16
|
|
11
17
|
# Handles message request. Each request has three parameters: channel id,
|
@@ -21,20 +27,18 @@ module Pechkin
|
|
21
27
|
# deserialized json.
|
22
28
|
# @see Configuration
|
23
29
|
def handle(channel_id, msg_id, data)
|
24
|
-
channel_config =
|
25
|
-
|
26
|
-
message_config = substitute(data, fetch_message(channel_config, msg_id))
|
27
|
-
|
28
|
-
data = (message_config['variables'] || {}).merge(data)
|
29
|
-
template = message_config['template']
|
30
|
-
|
31
|
-
text = ''
|
32
|
-
text = template.render(data) unless template.nil?
|
33
|
-
|
30
|
+
channel_config, message_config, text =
|
31
|
+
prepare_message(channel_id, msg_id, data)
|
34
32
|
chats = channel_config.chat_ids
|
35
33
|
connector = channel_config.connector
|
36
34
|
|
37
|
-
|
35
|
+
if message_allowed?(message_config, data)
|
36
|
+
chats.map { |chat| connector.send_message(chat, text, message_config) }
|
37
|
+
else
|
38
|
+
logger.info "#{channel_id}/#{msg_id}: " \
|
39
|
+
"Skip sending message. Because it's not allowed"
|
40
|
+
[]
|
41
|
+
end
|
38
42
|
end
|
39
43
|
|
40
44
|
# Executes message handling and renders template using connector logic
|
@@ -47,20 +51,16 @@ module Pechkin
|
|
47
51
|
# deserialized json.
|
48
52
|
# @see Configuration
|
49
53
|
def preview(channel_id, msg_id, data)
|
50
|
-
channel_config =
|
51
|
-
|
52
|
-
message_config = substitute(data, fetch_message(channel_config, msg_id))
|
53
|
-
|
54
|
-
data = (message_config['variables'] || {}).merge(data)
|
55
|
-
template = message_config['template']
|
56
|
-
|
57
|
-
text = ''
|
58
|
-
text = template.render(data) unless template.nil?
|
59
|
-
|
54
|
+
channel_config, message_config, text =
|
55
|
+
prepare_message(channel_id, msg_id, data)
|
60
56
|
chats = channel_config.chat_ids
|
61
57
|
connector = channel_config.connector
|
62
58
|
|
63
|
-
|
59
|
+
if message_allowed?(message_config, data)
|
60
|
+
connector.preview(chats, text, message_config)
|
61
|
+
else
|
62
|
+
puts "No message sent beacuse it's not allowed"
|
63
|
+
end
|
64
64
|
end
|
65
65
|
|
66
66
|
def message?(channel_id, msg_id)
|
@@ -69,6 +69,10 @@ module Pechkin
|
|
69
69
|
|
70
70
|
private
|
71
71
|
|
72
|
+
def puts(msg)
|
73
|
+
@stdout.puts(msg)
|
74
|
+
end
|
75
|
+
|
72
76
|
# Find channel by it's id or trow ChannelNotFoundError
|
73
77
|
def fetch_channel(channel_id)
|
74
78
|
raise ChannelNotFoundError, channel_id unless channels.key?(channel_id)
|
@@ -84,6 +88,24 @@ module Pechkin
|
|
84
88
|
message_list[msg_id]
|
85
89
|
end
|
86
90
|
|
91
|
+
def message_allowed?(message_config, data)
|
92
|
+
message_matcher.matches?(message_config, data)
|
93
|
+
end
|
94
|
+
|
95
|
+
def prepare_message(channel_id, msg_id, data)
|
96
|
+
channel_config = fetch_channel(channel_id)
|
97
|
+
# Find message and try substitute values to message parameters.
|
98
|
+
message_config = substitute(data, fetch_message(channel_config, msg_id))
|
99
|
+
|
100
|
+
data = (message_config['variables'] || {}).merge(data)
|
101
|
+
template = message_config['template']
|
102
|
+
|
103
|
+
text = ''
|
104
|
+
text = template.render(data) unless template.nil?
|
105
|
+
|
106
|
+
[channel_config, message_config, text]
|
107
|
+
end
|
108
|
+
|
87
109
|
def substitute(data, message_desc)
|
88
110
|
substitute_recursive(Substitute.new(data), message_desc)
|
89
111
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Pechkin
|
2
|
+
class MessageMatchError < StandardError; end
|
3
|
+
# Allows to match message configuration against received data.
|
4
|
+
#
|
5
|
+
# Data is checked againts either allow or forbid rules. But not both at the
|
6
|
+
# same time. Each field can contain list of rules to check. 'allow' list means
|
7
|
+
# we need at least one matching rule to allow data processing. And 'forbid'
|
8
|
+
# list respectievly means we need at least one matching rule to decline data
|
9
|
+
# processing.
|
10
|
+
class MessageMatcher
|
11
|
+
KEY_ALLOW = 'allow'.freeze
|
12
|
+
KEY_FORBID = 'forbid'.freeze
|
13
|
+
# Checks data object against allow / forbid rule sets in message
|
14
|
+
# configuration. If data object matches rules it means we can process this
|
15
|
+
# data and send message.
|
16
|
+
#
|
17
|
+
# @param message_config [Hash] message description.
|
18
|
+
# @param data [Hash] request object that need to be inspected whether we
|
19
|
+
# should process this data or not
|
20
|
+
# @return [Boolean] is data object matches message_config rules or not
|
21
|
+
def matches?(message_config, data)
|
22
|
+
check(message_config)
|
23
|
+
|
24
|
+
if message_config.key?(KEY_ALLOW)
|
25
|
+
rules = message_config[KEY_ALLOW]
|
26
|
+
rules.any? { |r| check_rule(r, data) }
|
27
|
+
elsif message_config.key?(KEY_FORBID)
|
28
|
+
rules = message_config[KEY_FORBID]
|
29
|
+
rules.all? { |r| !check_rule(r, data) }
|
30
|
+
else
|
31
|
+
true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def check(msg)
|
38
|
+
return unless msg.key?(KEY_ALLOW) && msg.key?(KEY_FORBID)
|
39
|
+
|
40
|
+
raise MessageMatchError, 'Both allow and forbid present in message config'
|
41
|
+
end
|
42
|
+
|
43
|
+
# Check rule object against data. Rules are checked recursievly, i.e. we
|
44
|
+
# can take one field in rule and check it separately as new rule. If all
|
45
|
+
# fields are passed check then whole rule passed.
|
46
|
+
def check_rule(rule, data)
|
47
|
+
if rule.is_a?(Hash)
|
48
|
+
check_hash_rule(rule, data)
|
49
|
+
elsif rule.is_a?(Array)
|
50
|
+
check_array_rule(rule, data)
|
51
|
+
elsif rule.is_a?(String)
|
52
|
+
check_string_rule(rule, data)
|
53
|
+
else
|
54
|
+
rule.eql?(data)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def check_hash_rule(hash, data)
|
59
|
+
return false unless data.is_a?(Hash)
|
60
|
+
|
61
|
+
hash.all? do |key, value|
|
62
|
+
data.key?(key) && check_rule(value, data[key])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def check_array_rule(array, data)
|
67
|
+
return false unless data.is_a?(Array)
|
68
|
+
|
69
|
+
# Deep array check needs to be done against all elements so we zip arrays
|
70
|
+
# to pair each rule with data element
|
71
|
+
array.zip(data).all? { |r, d| check_rule(r, d) }
|
72
|
+
end
|
73
|
+
|
74
|
+
def check_string_rule(str, data)
|
75
|
+
str.eql? data
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/pechkin/version.rb
CHANGED
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pechkin
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ilya Arkhanhelsky
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-02-
|
11
|
+
date: 2020-02-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: grape
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - '='
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: 1.1.0
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - '='
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: 1.1.0
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: htauth
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -90,6 +76,10 @@ files:
|
|
90
76
|
- bin/pechkin
|
91
77
|
- lib/pechkin.rb
|
92
78
|
- lib/pechkin/app.rb
|
79
|
+
- lib/pechkin/app/app.rb
|
80
|
+
- lib/pechkin/app/app_builder.rb
|
81
|
+
- lib/pechkin/app/app_error.rb
|
82
|
+
- lib/pechkin/app/request_handler.rb
|
93
83
|
- lib/pechkin/auth.rb
|
94
84
|
- lib/pechkin/channel.rb
|
95
85
|
- lib/pechkin/cli.rb
|
@@ -111,6 +101,7 @@ files:
|
|
111
101
|
- lib/pechkin/connector_telegram.rb
|
112
102
|
- lib/pechkin/exceptions.rb
|
113
103
|
- lib/pechkin/handler.rb
|
104
|
+
- lib/pechkin/message_matcher.rb
|
114
105
|
- lib/pechkin/message_template.rb
|
115
106
|
- lib/pechkin/prometheus_utils.rb
|
116
107
|
- lib/pechkin/substitute.rb
|