code_review_notifier 0.1.3 → 0.2.4
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/Gemfile +2 -3
- data/bin/code_review_notifier +2 -2
- data/lib/code_change_notification.rb +28 -0
- data/lib/code_review_notifier.rb +55 -45
- data/lib/gerrit_api.rb +40 -10
- data/lib/models/code_change.rb +2 -5
- data/lib/models/code_change_activity.rb +4 -2
- data/migrations.rb +8 -0
- metadata +6 -8
- data/Brewfile +0 -2
- data/lib/base_api.rb +0 -62
- data/lib/cipher.rb +0 -31
- data/lib/database.rb +0 -48
- data/lib/notifier.rb +0 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fe98a79405c30d5a0ab08d8b8c3f0693cd1605fc81c413ddcfac136e52c54105
|
4
|
+
data.tar.gz: 0a31a14ea309f530e8927962f109e8660c6d63a1bad7da504ca46091b01fba92
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 89a05dabf9bb56d0e43cd6295ee64cfd01b114416b420b84cb8147876d047f66533d490820fecda9dea2858877a56ca61b1cc66f053e9b96f07aea952e257c71
|
7
|
+
data.tar.gz: 9a2af8b575e1a47781820937fd47b8ee6bc7b2c4dbe5356a470aeb721a60eaa762961ab7b9d36bf53e7cd3c2063661bbca7bb4fc4f2c8088de0b7b11ecb424b2
|
data/Gemfile
CHANGED
data/bin/code_review_notifier
CHANGED
@@ -0,0 +1,28 @@
|
|
1
|
+
require "rubiclifier"
|
2
|
+
require_relative "./api.rb"
|
3
|
+
|
4
|
+
class CodeChangeNotification
|
5
|
+
attr_reader :code_change_activity
|
6
|
+
private :code_change_activity
|
7
|
+
|
8
|
+
def initialize(code_change_activity)
|
9
|
+
@code_change_activity = code_change_activity
|
10
|
+
end
|
11
|
+
|
12
|
+
def send
|
13
|
+
code_change = code_change_activity.code_change
|
14
|
+
id = code_change.id
|
15
|
+
owner = code_change.owner
|
16
|
+
subject = code_change.subject
|
17
|
+
|
18
|
+
message = code_change_activity.message
|
19
|
+
author = code_change_activity.author
|
20
|
+
Rubiclifier::Notification.new(
|
21
|
+
author,
|
22
|
+
message,
|
23
|
+
"#{owner}: #{subject}",
|
24
|
+
Api.current_api.favicon,
|
25
|
+
Api.current_api.code_change_url(code_change)
|
26
|
+
).send
|
27
|
+
end
|
28
|
+
end
|
data/lib/code_review_notifier.rb
CHANGED
@@ -1,48 +1,24 @@
|
|
1
|
-
require "
|
1
|
+
require "rubiclifier"
|
2
2
|
require_relative "./api.rb"
|
3
|
-
require_relative "./
|
3
|
+
require_relative "./code_change_notification.rb"
|
4
4
|
|
5
5
|
SECONDS_BETWEEN_RUNS = 90
|
6
6
|
SECONDS_BETWEEN_NOTIFICATIONS = 5
|
7
7
|
|
8
|
-
class CodeReviewNotifier
|
9
|
-
def
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
print("What's the account password? (hiding input) ")
|
21
|
-
DB.save_setting("password", STDIN.noecho(&:gets).chomp, is_secret: true)
|
22
|
-
puts
|
23
|
-
|
24
|
-
print("What's the account id? (check #{Api.current_api.base_api_url}/settings/) ")
|
25
|
-
DB.save_setting("account_id", STDIN.gets.chomp, is_secret: false)
|
26
|
-
|
27
|
-
puts("All setup!")
|
28
|
-
puts
|
29
|
-
puts("It's recommended that you set this up as a system service with serviceman. Check it out here: https://git.rootprojects.org/root/serviceman")
|
30
|
-
puts("Set code_review_notifier to run on startup with `serviceman add --name code_review_notifier code_review_notifier`")
|
31
|
-
exit
|
32
|
-
end
|
33
|
-
|
34
|
-
if !Api.current_api.is_setup?
|
35
|
-
Notifier.notify("Missing Setup Info", "Run `code_review_notifier --setup` to setup.")
|
36
|
-
puts
|
37
|
-
puts("You must finish setup first by running with the `--setup` option.")
|
38
|
-
puts("`code_review_notifier --setup`")
|
39
|
-
exit
|
40
|
-
end
|
8
|
+
class CodeReviewNotifier < Rubiclifier::BaseApplication
|
9
|
+
def show_help
|
10
|
+
puts
|
11
|
+
puts("This polls for updates to patch sets/pull requests and notifies you about any relevant changes.")
|
12
|
+
puts
|
13
|
+
puts("Usage:")
|
14
|
+
puts(" code_review_notifier --help | Shows this help menu")
|
15
|
+
puts(" code_review_notifier --setup | Runs setup")
|
16
|
+
puts(" code_review_notifier | Start listening for changes (should be run in background)")
|
17
|
+
puts
|
18
|
+
exit
|
41
19
|
end
|
42
20
|
|
43
|
-
def
|
44
|
-
setup(args)
|
45
|
-
|
21
|
+
def run_application
|
46
22
|
while true
|
47
23
|
is_first_run = is_first_run?
|
48
24
|
puts
|
@@ -51,16 +27,14 @@ class CodeReviewNotifier
|
|
51
27
|
puts("Checking for notifications to display...")
|
52
28
|
all_activity = []
|
53
29
|
all_code_changes.each do |cc|
|
54
|
-
|
55
|
-
|
56
|
-
cc.activity_from_self_at = activity_for_code_change.find { |a| a.is_self }&.created_at
|
57
|
-
all_activity.concat(activity_for_code_change)
|
30
|
+
cc.code_change_activity.sort! { |a, b| a.created_at <=> b.created_at }
|
31
|
+
all_activity.concat(cc.code_change_activity)
|
58
32
|
end
|
59
33
|
all_activity.select(&:should_notify?).each do |code_change_activity|
|
60
34
|
code_change_activity.notified
|
61
35
|
unless is_first_run
|
62
36
|
puts("Notifying of change!")
|
63
|
-
|
37
|
+
CodeChangeNotification.new(code_change_activity).send
|
64
38
|
sleep(SECONDS_BETWEEN_NOTIFICATIONS)
|
65
39
|
end
|
66
40
|
end
|
@@ -68,7 +42,43 @@ class CodeReviewNotifier
|
|
68
42
|
end
|
69
43
|
end
|
70
44
|
|
71
|
-
def
|
72
|
-
|
45
|
+
def not_setup
|
46
|
+
Rubiclifier::Notification.new(
|
47
|
+
"Missing Setup Info",
|
48
|
+
"Run `code_review_notifier --setup` to setup."
|
49
|
+
).send
|
50
|
+
end
|
51
|
+
|
52
|
+
def features
|
53
|
+
[
|
54
|
+
Rubiclifier::Feature::BACKGROUND,
|
55
|
+
Rubiclifier::Feature::DATABASE,
|
56
|
+
Rubiclifier::Feature::NOTIFICATIONS
|
57
|
+
]
|
58
|
+
end
|
59
|
+
|
60
|
+
def settings
|
61
|
+
@settings ||= [
|
62
|
+
Rubiclifier::Setting.new("base_api_url", "base URL", explanation: "e.g. https://gerrit.google.com"),
|
63
|
+
Rubiclifier::Setting.new("username", "account username"),
|
64
|
+
Rubiclifier::Setting.new("password", "account password", explanation: "input hidden", is_secret: true),
|
65
|
+
Rubiclifier::Setting.new("account_id", "account ID", explanation: -> {"check #{Api.current_api.base_api_url}/settings/"})
|
66
|
+
]
|
67
|
+
end
|
68
|
+
|
69
|
+
def executable_name
|
70
|
+
"code_review_notifier"
|
71
|
+
end
|
72
|
+
|
73
|
+
def data_directory
|
74
|
+
"~/.code_review_notifier"
|
75
|
+
end
|
76
|
+
|
77
|
+
def migrations_location
|
78
|
+
"#{File.expand_path(File.dirname(__FILE__) + "/..")}/migrations.rb"
|
79
|
+
end
|
80
|
+
|
81
|
+
def is_first_run?
|
82
|
+
Rubiclifier::DB.query_single_row("SELECT id FROM code_change_activity_notified;").nil?
|
73
83
|
end
|
74
84
|
end
|
data/lib/gerrit_api.rb
CHANGED
@@ -1,9 +1,15 @@
|
|
1
|
-
|
1
|
+
require "rubiclifier"
|
2
2
|
require_relative "./models/code_change.rb"
|
3
3
|
require_relative "./models/code_change_activity.rb"
|
4
4
|
|
5
|
-
class
|
6
|
-
def
|
5
|
+
class HTTParty::Parser
|
6
|
+
def json
|
7
|
+
JSON.parse(body.gsub(")]}'", ""))
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class GerritApi < Rubiclifier::BaseApi
|
12
|
+
def self.login_and_get_api_token
|
7
13
|
res = post("/login/", {
|
8
14
|
body: "username=#{username}&password=#{URI.escape(password)}&rememberme=1",
|
9
15
|
headers: {
|
@@ -12,19 +18,23 @@ class GerritApi < BaseApi
|
|
12
18
|
follow_redirects: false
|
13
19
|
})
|
14
20
|
set_cookie_header = res.headers["set-cookie"] || ""
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
+
set_cookie_header.match("GerritAccount=(.*?);")&.to_a&.fetch(1)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.invalid_credentials_error
|
25
|
+
Rubiclifier::Notification.new(
|
26
|
+
"Incorrect Credentials",
|
27
|
+
"Trying running `code_review_notifier --setup` again."
|
28
|
+
).send
|
29
|
+
sleep(120)
|
30
|
+
exit
|
21
31
|
end
|
22
32
|
|
23
33
|
def self.all_code_changes
|
24
34
|
wrap_with_authentication do
|
25
35
|
get("/changes/?S=0&q=is%3Aopen%20owner%3Aself%20-is%3Awip%20-is%3Aignored%20limit%3A25&q=is%3Aopen%20owner%3Aself%20is%3Awip%20limit%3A25&q=is%3Aopen%20-owner%3Aself%20-is%3Awip%20-is%3Aignored%20reviewer%3Aself%20limit%3A25&q=is%3Aclosed%20-is%3Aignored%20%28-is%3Awip%20OR%20owner%3Aself%29%20%28owner%3Aself%20OR%20reviewer%3Aself%29%20-age%3A4w%20limit%3A10&o=DETAILED_ACCOUNTS&o=MESSAGES", {
|
26
36
|
headers: {
|
27
|
-
"Cookie" => "GerritAccount=#{
|
37
|
+
"Cookie" => "GerritAccount=#{api_token};"
|
28
38
|
}
|
29
39
|
})
|
30
40
|
end.parsed_response.flat_map { |js| js.map { |j| code_change_from_json(j) } }
|
@@ -47,4 +57,24 @@ class GerritApi < BaseApi
|
|
47
57
|
def self.code_change_url(code_change)
|
48
58
|
"#{base_api_url}/c/#{code_change.id}"
|
49
59
|
end
|
60
|
+
|
61
|
+
def self.api_token_db_key
|
62
|
+
"api_token"
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.base_api_url_db_key
|
66
|
+
"base_api_url"
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.username
|
70
|
+
@username ||= Rubiclifier::DB.get_setting("username")
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.password
|
74
|
+
@password ||= Rubiclifier::DB.get_setting("password")
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.account_id
|
78
|
+
@account_id ||= Rubiclifier::DB.get_setting("account_id")
|
79
|
+
end
|
50
80
|
end
|
data/lib/models/code_change.rb
CHANGED
@@ -1,21 +1,18 @@
|
|
1
1
|
require "date"
|
2
|
-
require_relative "../database.rb"
|
3
2
|
|
4
3
|
class CodeChange
|
5
4
|
attr_accessor :code_change_activity
|
6
5
|
attr_reader :id, :owner, :project, :subject, :updated_at
|
7
|
-
attr_writer :activity_from_self_at
|
8
6
|
|
9
7
|
def initialize(id, owner, project, subject, updated_at)
|
10
8
|
@id = id
|
11
9
|
@owner = owner
|
12
10
|
@project = project
|
13
|
-
@subject = subject.gsub("'", "")
|
11
|
+
@subject = subject.gsub("'", "’")
|
14
12
|
@updated_at = updated_at
|
15
13
|
end
|
16
14
|
|
17
15
|
def activity_from_self_at
|
18
|
-
|
19
|
-
@activity_from_self_at
|
16
|
+
@activity_from_self_at ||= code_change_activity.find { |a| a.is_self }&.created_at
|
20
17
|
end
|
21
18
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require "rubiclifier"
|
2
|
+
|
1
3
|
MESSAGES_TO_IGNORE = [/Uploaded patch set 1/, /Build Started/, /owns \d+% of/]
|
2
4
|
AUTHOR_TRANSLATIONS = {
|
3
5
|
"Service Cloud Jenkins" => "Jenkins",
|
@@ -17,14 +19,14 @@ class CodeChangeActivity
|
|
17
19
|
end
|
18
20
|
|
19
21
|
def notified
|
20
|
-
DB.execute("INSERT INTO code_change_activity_notified (id, notified_at) VALUES('#{id}', CURRENT_TIMESTAMP);")
|
22
|
+
Rubiclifier::DB.execute("INSERT INTO code_change_activity_notified (id, notified_at) VALUES('#{id}', CURRENT_TIMESTAMP);")
|
21
23
|
end
|
22
24
|
|
23
25
|
def should_notify?
|
24
26
|
!is_self &&
|
25
27
|
code_change.activity_from_self_at && created_at > code_change.activity_from_self_at &&
|
26
28
|
MESSAGES_TO_IGNORE.none? { |m| message.match(m) } &&
|
27
|
-
!DB.query_single_row("SELECT id FROM code_change_activity_notified WHERE id = '#{id}'")
|
29
|
+
!Rubiclifier::DB.query_single_row("SELECT id FROM code_change_activity_notified WHERE id = '#{id}'")
|
28
30
|
end
|
29
31
|
|
30
32
|
def self.translate_author(author)
|
data/migrations.rb
ADDED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: code_review_notifier
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kyle Grinstead
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-07-
|
11
|
+
date: 2020-07-10 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email: kyleag@hey.com
|
@@ -17,24 +17,22 @@ executables:
|
|
17
17
|
extensions: []
|
18
18
|
extra_rdoc_files: []
|
19
19
|
files:
|
20
|
-
- Brewfile
|
21
20
|
- Gemfile
|
22
21
|
- bin/code_review_notifier
|
23
22
|
- lib/api.rb
|
24
|
-
- lib/
|
25
|
-
- lib/cipher.rb
|
23
|
+
- lib/code_change_notification.rb
|
26
24
|
- lib/code_review_notifier.rb
|
27
|
-
- lib/database.rb
|
28
25
|
- lib/gerrit_api.rb
|
29
26
|
- lib/models/code_change.rb
|
30
27
|
- lib/models/code_change_activity.rb
|
31
|
-
-
|
28
|
+
- migrations.rb
|
32
29
|
homepage: https://rubygems.org/gems/code_review_notifier
|
33
30
|
licenses:
|
34
31
|
- MIT
|
35
32
|
metadata:
|
36
33
|
source_code_uri: https://github.com/MrGrinst/code_review_notifier
|
37
|
-
post_install_message:
|
34
|
+
post_install_message: "\n\e[32mThanks for installing code_review_notifier!\e[0m\n\e[32mSet
|
35
|
+
it up by running `\e[0mcode_review_notifier --setup\e[32m`\e[0m\n\n"
|
38
36
|
rdoc_options: []
|
39
37
|
require_paths:
|
40
38
|
- lib
|
data/Brewfile
DELETED
data/lib/base_api.rb
DELETED
@@ -1,62 +0,0 @@
|
|
1
|
-
require "httparty"
|
2
|
-
require_relative "./gerrit_api.rb"
|
3
|
-
|
4
|
-
class HTTParty::Parser
|
5
|
-
def json
|
6
|
-
JSON.parse(body.gsub(")]}'", ""))
|
7
|
-
end
|
8
|
-
end
|
9
|
-
|
10
|
-
class BaseApi
|
11
|
-
include HTTParty
|
12
|
-
|
13
|
-
def self.authenticate
|
14
|
-
raise NotImplementedError
|
15
|
-
end
|
16
|
-
|
17
|
-
def self.all_code_changes
|
18
|
-
raise NotImplementedError
|
19
|
-
end
|
20
|
-
|
21
|
-
def self.favicon
|
22
|
-
raise NotImplementedError
|
23
|
-
end
|
24
|
-
|
25
|
-
def self.code_change_url(code_change)
|
26
|
-
raise NotImplementedError
|
27
|
-
end
|
28
|
-
|
29
|
-
def self.wrap_with_authentication(&block)
|
30
|
-
res = block.call
|
31
|
-
if res.code == 401 || res.code == 403
|
32
|
-
self.authenticate
|
33
|
-
block.call
|
34
|
-
else
|
35
|
-
res
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def self.is_setup?
|
40
|
-
base_api_url && username && password && account_id
|
41
|
-
end
|
42
|
-
|
43
|
-
def self.base_api_url
|
44
|
-
@base_api_url ||= begin
|
45
|
-
url = DB.get_setting("base_api_url")
|
46
|
-
base_uri(url)
|
47
|
-
url.chomp("/")
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def self.username
|
52
|
-
@username ||= DB.get_setting("username")
|
53
|
-
end
|
54
|
-
|
55
|
-
def self.password
|
56
|
-
@password ||= DB.get_setting("password")
|
57
|
-
end
|
58
|
-
|
59
|
-
def self.account_id
|
60
|
-
@account_id ||= DB.get_setting("account_id")
|
61
|
-
end
|
62
|
-
end
|
data/lib/cipher.rb
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
require "base64"
|
2
|
-
require "openssl"
|
3
|
-
require "digest/sha1"
|
4
|
-
|
5
|
-
KEY = ""
|
6
|
-
|
7
|
-
class Cipher
|
8
|
-
def self.encrypt(text)
|
9
|
-
cipher = OpenSSL::Cipher.new("aes-256-cbc")
|
10
|
-
cipher.encrypt
|
11
|
-
key = Digest::SHA1.hexdigest("yourpass")[0..31]
|
12
|
-
iv = cipher.random_iv
|
13
|
-
cipher.key = key
|
14
|
-
cipher.iv = iv
|
15
|
-
encrypted = cipher.update(text)
|
16
|
-
encrypted << cipher.final
|
17
|
-
["#{key}:#{Base64.encode64(iv).encode('utf-8')}", Base64.encode64(encrypted).encode('utf-8')]
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.decrypt(encrypted, salt)
|
21
|
-
key, iv = salt.split(":")
|
22
|
-
iv = Base64.decode64(iv.encode('ascii-8bit'))
|
23
|
-
cipher = OpenSSL::Cipher.new("aes-256-cbc")
|
24
|
-
cipher.decrypt
|
25
|
-
cipher.key = key
|
26
|
-
cipher.iv = iv
|
27
|
-
decrypted = cipher.update(Base64.decode64(encrypted.encode('ascii-8bit')))
|
28
|
-
decrypted << cipher.final
|
29
|
-
decrypted
|
30
|
-
end
|
31
|
-
end
|
data/lib/database.rb
DELETED
@@ -1,48 +0,0 @@
|
|
1
|
-
require "sqlite3"
|
2
|
-
require_relative "./cipher.rb"
|
3
|
-
|
4
|
-
class DB
|
5
|
-
def self.db
|
6
|
-
@db ||= begin
|
7
|
-
db = SQLite3::Database.new(File.expand_path("~/.code_review_notifier/data.db"))
|
8
|
-
migrate_if_needed(db)
|
9
|
-
db
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
def self.migrate_if_needed(db)
|
14
|
-
db.execute("CREATE TABLE IF NOT EXISTS code_change_activity_notified (id VARCHAR(50) PRIMARY KEY, notified_at TIMESTAMP);")
|
15
|
-
db.execute("CREATE TABLE IF NOT EXISTS settings (key VARCHAR(50) PRIMARY KEY, value TEXT, salt TEXT);")
|
16
|
-
end
|
17
|
-
|
18
|
-
def self.execute(sql)
|
19
|
-
db.execute(sql)
|
20
|
-
end
|
21
|
-
|
22
|
-
def self.query_single_row(sql)
|
23
|
-
db.execute(sql) do |row|
|
24
|
-
return row
|
25
|
-
end
|
26
|
-
return nil
|
27
|
-
end
|
28
|
-
|
29
|
-
def self.save_setting(key, value, is_secret:)
|
30
|
-
salt = "NULL"
|
31
|
-
if is_secret
|
32
|
-
salt, encrypted = Cipher.encrypt(value)
|
33
|
-
salt = "'#{salt}'"
|
34
|
-
value = encrypted
|
35
|
-
end
|
36
|
-
db.execute("DELETE FROM settings WHERE key = '#{key}';")
|
37
|
-
db.execute("INSERT INTO settings (key, value, salt) VALUES('#{key}', '#{value}', #{salt});")
|
38
|
-
end
|
39
|
-
|
40
|
-
def self.get_setting(key)
|
41
|
-
row = query_single_row("SELECT value, salt FROM settings WHERE key = '#{key}'")
|
42
|
-
if row && row[1]
|
43
|
-
Cipher.decrypt(row[0], row[1])
|
44
|
-
elsif row
|
45
|
-
row[0]
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
data/lib/notifier.rb
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
require_relative './api.rb'
|
2
|
-
|
3
|
-
class Notifier
|
4
|
-
def self.notify_about_code_change(code_change_activity)
|
5
|
-
code_change = code_change_activity.code_change
|
6
|
-
id = code_change.id
|
7
|
-
owner = code_change.owner
|
8
|
-
subject = code_change.subject
|
9
|
-
|
10
|
-
message = code_change_activity.message
|
11
|
-
author = code_change_activity.author
|
12
|
-
notify(author, message, "#{owner}: #{subject}", Api.current_api.favicon, Api.current_api.code_change_url(code_change))
|
13
|
-
end
|
14
|
-
|
15
|
-
def self.notify(title, message, subtitle = nil, icon = nil, url = nil)
|
16
|
-
args = {
|
17
|
-
"title" => title,
|
18
|
-
"message" => message,
|
19
|
-
"subtitle" => subtitle,
|
20
|
-
"appIcon" => icon,
|
21
|
-
"open" => url
|
22
|
-
}
|
23
|
-
all_args = args.keys.reduce("") do |arg_string, key|
|
24
|
-
if args[key]
|
25
|
-
arg_string += " -#{key} '#{args[key]}'"
|
26
|
-
end
|
27
|
-
arg_string
|
28
|
-
end
|
29
|
-
system("/usr/local/bin/terminal-notifier #{all_args}")
|
30
|
-
end
|
31
|
-
end
|