/
notifier.clj
185 lines (165 loc) · 7.35 KB
/
notifier.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
(ns kaocha.plugin.notifier
"Plugin that shows the test result (fail/pass) and test counts in a
desktop (system tray) notification.
Will try multiple approaches to find a system-appropriate way to show a
desktop notifaction.
- notify-send
- terminal-notifier
- java.awt.SystemTray
"
{:authors ["Ryan McCuaig (@rgm)"
"Arne Brasseur (@plexus)"]}
(:require [clojure.java.shell :refer [sh]]
[kaocha.output :as output]
[kaocha.plugin :refer [defplugin]]
[kaocha.result :as result]
[kaocha.platform :as platform]
[kaocha.shellwords :refer [shellwords]]
[clojure.string :as str]
[clojure.java.io :as io]
[slingshot.slingshot :refer [throw+]])
(:import [java.nio.file Files]
[java.io IOException]
[java.awt SystemTray TrayIcon TrayIcon$MessageType Toolkit]))
;; special thanks for terminal-notify stuff to
;; https://github.com/glittershark/midje-notifier/blob/master/src/midje/notifier.clj
(defn exists? [program]
(let [cmd (if (platform/on-windows?)
"where.exe" "which" )]
(try
(= 0 (:exit (sh cmd program)))
(catch IOException e ;in the unlikely event neither where.exe nor which is available
(output/warn (format "Unable to determine whether '%s' exists. Notifications may not work." program)) ))))
(defn detect-command []
(cond
(exists? "notify-send")
"notify-send -a Kaocha %{title} %{message} -i %{icon} -u %{urgency} -t %{timeout}"
(exists? "terminal-notifier")
"terminal-notifier -message %{message} -title %{title} -appIcon %{icon}"))
(defn message [result]
(let [{::result/keys [count pass fail error pending]} (result/testable-totals result)]
(str count " tests"
(when (pos-int? error)
(str ", " error " errors"))
(when (pos-int? pending)
(str ", "pending " pending"))
(when (pos-int? fail)
(str ", " fail " failures"))
".")))
(defn title [result]
(if (result/failed? result)
"⛔️ Failing"
"✅ Passing"))
(def icon-path
"Return a local path to the Clojure icon.
If Kaocha is running from a jar, then extract the icon to a temp file first,
so external programs have access to it. "
(memoize
(fn []
(let [resource (io/resource "kaocha/clojure_logo.png")]
(if (= "file" (.getProtocol resource))
(str (io/file resource))
(let [file (java.io.File/createTempFile "clojure_logo" ".png")]
(io/copy (io/make-input-stream resource {}) file)
(str file)))))))
(def tray-icon
"Creates a system tray icon."
(memoize
(fn [icon-path]
(let [^java.awt.Toolkit toolkit (Toolkit/getDefaultToolkit)
tray-icon (-> toolkit
(.getImage ^String icon-path)
(TrayIcon. "Kaocha Notification"))]
(doto (SystemTray/getSystemTray)
(.add tray-icon))
tray-icon))))
(defn send-tray-notification
"Use Java's built-in functionality to display a notification.
Not preferred over shelling out because the built-in notification sometimes
looks out of place, and isn't consistently available on Linux."
[result]
(try
(let [icon (tray-icon "kaocha/clojure_logo.png")
urgency (if (result/failed? result) TrayIcon$MessageType/ERROR TrayIcon$MessageType/INFO) ]
(.displayMessage icon (title result) (message result) urgency))
(catch java.awt.HeadlessException _e
(output/warn (str "Notification not shown because system is headless."
"\nConsider disabling the notifier plugin when using in this context.")))
(catch java.lang.UnsupportedOperationException _e
(output/warn (str "Notification not shown because system does not support it."
"\nConsider disabling the notifier plugin when using in this context or installing"
"\neither notify-send (Linux) or terminal-notifier (macOS).")))))
(defn expand-command
"Takes a command string including replacement patterns, and a map of
replacements, and returns a vector of command + arguments.
Replacement patterns are of the form `%{xxx}`"
[command replacements]
(map (fn [cmd]
(reduce
(fn [cmd [k v]]
(str/replace cmd (str "%{" (name k) "}") (str v)))
cmd
replacements))
(shellwords command)))
(defn run-command
"Run the given command string, replacing patterns with values based on the given
test result."
[command result]
(let [{::result/keys [count pass fail error pending]} (result/testable-totals result)
timeout (::timeout result)
message (message result)
title (title result)
icon (icon-path)
failed? (result/failed? result)
urgency (if failed? "critical" "normal")
expanded-command (expand-command command {:message message
:title title
:icon icon
:urgency urgency
:count count
:pass pass
:fail fail
:error error
:pending pending
:failed? failed?
:timeout timeout})
{:keys [exit err] :as command-result} (apply sh expanded-command)]
(when (not (zero? exit))
(output/warn (format
(str
"Notification command exited with status code: %s"
"Error message (stderr): \"%s\""
"Command: \"%s\""
"Check your configuration for :kaocha.plugin.notifier/command and :kaocha.plugin.notifier/notification-timeout.")
exit
err
(apply str (interpose \space expanded-command)))))
command-result))
(defplugin kaocha.plugin/notifier
"Run a shell command after each completed test run, by default will run a
command that displays a desktop notification showing the test results, so a
`kaocha --watch` terminal process can be hidden and generally ignored until it
goes red.
Requires https://github.com/julienXX/terminal-notifier on mac or `libnotify` /
`notify-send` on linux."
(cli-options [opts]
(conj opts
[nil "--[no-]notifications" "Enable/disable the notifier plugin, providing desktop notifications. Defaults to true."]
[nil "--notification-timeout TIMEOUT" "Set a timeout value for desktop notifications through the notifier plugin."]))
(config [config]
(let [cli-options (:kaocha/cli-options config)
cli-flag (:notifications cli-options)
timeout (:notification-timeout cli-options (::timeout config -1))]
(assoc config
::command (::command config (detect-command))
::timeout timeout
::notifications?
(if (some? cli-flag)
cli-flag
(::notifications? config true)))))
(post-run [result]
(when (::notifications? result)
(if-let [command (::command result)]
(run-command command result)
(send-tray-notification result)))
result))