tellmewhen 1.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.
- data/README.textile +50 -0
- data/Rakefile +10 -0
- data/bin/tellmewhen +383 -0
- data/tellmewhen.gemspec +28 -0
- metadata +79 -0
data/README.textile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
h1. tellmewhen
|
2
|
+
|
3
|
+
Tell me when another program has finshed. Show me when it started, when it finished and how long it took. Show me the output and if it errored.
|
4
|
+
|
5
|
+
I created this utility because I had long running database scripts that I wanted a completion notification for - including a summary of the time and exit code. I had done this various ways in the past by using bash and other shell utilities. This program captures all of the features and behavior I wanted without having to script it from scratch again every time I needed it.
|
6
|
+
|
7
|
+
* YML Based Configuration in an rc file
|
8
|
+
* easy default configuration
|
9
|
+
* sends email
|
10
|
+
* includes start/stop/elapsed timing
|
11
|
+
* success/fail based on exit code
|
12
|
+
* watch an already running process (by pid, by grep pattern over 'ps aux')
|
13
|
+
* release as a gem so it's easy to use/install
|
14
|
+
|
15
|
+
h1. Configuration
|
16
|
+
|
17
|
+
Configuration Defaults:
|
18
|
+
|
19
|
+
Configuration is then merged from @$HOME/.tellmewhenrc@ if the file exists in @$HOME@. Configuration is also merged from @./.tellmewhenrc@ in the current directory, if the file exists. This allows you some flexibility in overriding settings.
|
20
|
+
|
21
|
+
h1. Usage
|
22
|
+
|
23
|
+
pre. Usage: ./tellmewhen command args...
|
24
|
+
-v, --[no-]verbose Run Verbosely.
|
25
|
+
-c, --config=file Use alternate configuration file.
|
26
|
+
-p, --pid=pid Wait for <pid> to terminate.
|
27
|
+
-e, --exists=file Wait for <file> to exist.
|
28
|
+
-m, --modified=file Wait for <file> to be modified.
|
29
|
+
-t, --timeout=seconds Wait for up to <seconds> seconds for the command before sendin a 'pending' notification.
|
30
|
+
-w, --write-config=file Write [fully merged] configuration to <file> (NB: will not be clobber).
|
31
|
+
|
32
|
+
|
33
|
+
h1. Examples
|
34
|
+
|
35
|
+
pre. ./tellmewhen 'sleep 3; ls'
|
36
|
+
./tellmewhen -p 12345
|
37
|
+
./tellmewhen -e some-file.txt # await existance
|
38
|
+
./tellmewhen -m some-file.txt # await update
|
39
|
+
|
40
|
+
h1. Authors
|
41
|
+
|
42
|
+
h1. License
|
43
|
+
|
44
|
+
h1. Patches Welcome
|
45
|
+
|
46
|
+
I'd like to clean up the internals: how emails are composed.
|
47
|
+
|
48
|
+
I'd like to support other notification channels, like Instant Messaging or SMS. Multiple at one time, controlled through the configuration yaml.
|
49
|
+
|
50
|
+
What would you like?
|
data/Rakefile
ADDED
data/bin/tellmewhen
ADDED
@@ -0,0 +1,383 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'yaml'
|
5
|
+
require 'optparse'
|
6
|
+
require 'tempfile'
|
7
|
+
require 'net/smtp'
|
8
|
+
|
9
|
+
class TellMeWhen
|
10
|
+
RC_FILE = "#{ENV['HOME']}/.tellmewhenrc"
|
11
|
+
LOCAL_FILE = ".tellmewhenrc"
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@stdout_file = Tempfile.new('tellmewhen.stdout').path
|
15
|
+
@stderr_file = Tempfile.new('tellmewhen.stdout').path
|
16
|
+
end
|
17
|
+
|
18
|
+
def hostname
|
19
|
+
`hostname`.chomp
|
20
|
+
end
|
21
|
+
|
22
|
+
def load_settings
|
23
|
+
@settings ||= {
|
24
|
+
'notify-via' => 'email',
|
25
|
+
'email' => {
|
26
|
+
'to' => "#{ENV['LOGNAME']}@#{hostname}",
|
27
|
+
'from' => "#{ENV['LOGNAME']}@#{hostname}",
|
28
|
+
'smtp_host' => 'localhost',
|
29
|
+
'smtp_port' => '25'
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
if File.exist? RC_FILE
|
34
|
+
settings = YAML.load_file RC_FILE
|
35
|
+
@settings = @settings.merge(settings)
|
36
|
+
puts "Loaded Settings [#{RC_FILE}]: #{@settings.inspect}"
|
37
|
+
end
|
38
|
+
|
39
|
+
if File.exist? LOCAL_FILE
|
40
|
+
settings = YAML.load_file LOCAL_FILE
|
41
|
+
@settings = @settings.merge(settings)
|
42
|
+
puts "Loaded Settings [#{LOCAL_FILE}]: #{@settings.inspect}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def save_settings target_file=RC_FILE
|
47
|
+
if ! File.exist? target_file
|
48
|
+
File.open(target_file,"w") do |f|
|
49
|
+
f.write @settings.to_yaml
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def parse_options
|
55
|
+
@options = {
|
56
|
+
:wait_on => :command,
|
57
|
+
:wait_timeout => 600 # every 10 min send a 'pending' email
|
58
|
+
}
|
59
|
+
OptionParser.new do |opts|
|
60
|
+
opts.banner = "Usage: #$0 command args..."
|
61
|
+
|
62
|
+
opts.on("-v","--[no-]verbose", "Run Verbosely.") do |v|
|
63
|
+
@options[:verbose] = v
|
64
|
+
end
|
65
|
+
|
66
|
+
opts.on("-c","--config=file", "Use alternate configuration file.") do |file|
|
67
|
+
@options[:config_file] = file
|
68
|
+
end
|
69
|
+
|
70
|
+
opts.on("-p","--pid=pid", "Wait for <pid> to terminate.") do |pid|
|
71
|
+
@options[:wait_on] = :pid
|
72
|
+
@options[:pid] = pid
|
73
|
+
end
|
74
|
+
|
75
|
+
opts.on("-e","--exists=file", "Wait for <file> to exist.") do |file|
|
76
|
+
@options[:wait_on] = :file_exists
|
77
|
+
@options[:trigger_file] = file
|
78
|
+
end
|
79
|
+
|
80
|
+
opts.on("-m","--modified=file", "Wait for <file> to be modified.") do |file|
|
81
|
+
@options[:wait_on] = :file_modified
|
82
|
+
@options[:trigger_file] = file
|
83
|
+
end
|
84
|
+
|
85
|
+
opts.on("-t","--timeout=seconds", "Wait for up to <seconds> seconds for the command before sendin a 'pending' notification.") do |seconds|
|
86
|
+
@options[:wait_timeout] = seconds
|
87
|
+
end
|
88
|
+
|
89
|
+
opts.on("-w","--write-config=file", "Write [fully merged] configuration to <file> (NB: will not be clobber).") do |file|
|
90
|
+
@options[:write_config_to] = file
|
91
|
+
end
|
92
|
+
|
93
|
+
end.parse!
|
94
|
+
|
95
|
+
puts "Options: #{@options.inspect}"
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.main args
|
99
|
+
app = self.new
|
100
|
+
app.parse_options
|
101
|
+
app.load_settings
|
102
|
+
exit app.run args
|
103
|
+
end
|
104
|
+
|
105
|
+
def elapsed_time
|
106
|
+
Time.now.to_i - @start_time.to_i
|
107
|
+
end
|
108
|
+
|
109
|
+
def wait_timeout
|
110
|
+
@options[:wait_timeout]
|
111
|
+
end
|
112
|
+
|
113
|
+
def wait_on_command args
|
114
|
+
puts "Do run: #{args}"
|
115
|
+
if args.to_s.empty?
|
116
|
+
raise "Error: you must supply a command to execute"
|
117
|
+
end
|
118
|
+
child_pid = Kernel.fork
|
119
|
+
if child_pid.nil?
|
120
|
+
# in child
|
121
|
+
STDOUT.reopen(File.open(@stdout_file, 'w+'))
|
122
|
+
STDERR.reopen(File.open(@stderr_file, 'w+'))
|
123
|
+
STDIN.close
|
124
|
+
exec "bash", "-c", args.to_s
|
125
|
+
else
|
126
|
+
# in parent
|
127
|
+
child_exited = false
|
128
|
+
while ! child_exited
|
129
|
+
if Process.wait child_pid, Process::WNOHANG
|
130
|
+
puts "Child exited: #{$?.exitstatus}"
|
131
|
+
@exit_status = $?.exitstatus
|
132
|
+
child_exited = true
|
133
|
+
end
|
134
|
+
sleep(0.250)
|
135
|
+
if elapsed_time > wait_timeout
|
136
|
+
puts "Exceeded timeout #{wait_timeout}, sending 'pending' notificaiton"
|
137
|
+
send_pending_notification
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
@end_time = Time.now
|
143
|
+
if @exit_status == 0
|
144
|
+
body = <<-BODY
|
145
|
+
Just wanted to let you know that:
|
146
|
+
|
147
|
+
#{args.to_s}
|
148
|
+
|
149
|
+
completed on #{hostname}, with an exit code of: #{@exit_status}
|
150
|
+
|
151
|
+
It started at #{@start_time.to_s} (#{@start_time.to_i}), finished at #{@end_time.to_s} (#{@end_time.to_i}) and took a total of #{elapsed_time} seconds.
|
152
|
+
|
153
|
+
May your day continue to be full of win.
|
154
|
+
|
155
|
+
Sincerely,
|
156
|
+
|
157
|
+
Tellmewhen
|
158
|
+
|
159
|
+
#{email_footer}
|
160
|
+
BODY
|
161
|
+
|
162
|
+
send_email_notification "When! SUCCESS for #{args.to_s.split.first}...", body
|
163
|
+
else
|
164
|
+
body = <<-BODY
|
165
|
+
Just wanted to let you know that:
|
166
|
+
|
167
|
+
#{args.to_s}
|
168
|
+
|
169
|
+
FAILED! on #{hostname}, with an exit code of: #{@exit_status}
|
170
|
+
|
171
|
+
It started at #{@start_time.to_s} (#{@start_time.to_i}), finished at #{@end_time.to_s} (#{@end_time.to_i}) and took a total of #{elapsed_time} seconds to collapse in a steaming heap of failure.
|
172
|
+
|
173
|
+
Have a nice day.
|
174
|
+
|
175
|
+
Warmest Regards,
|
176
|
+
|
177
|
+
Tellmewhen
|
178
|
+
|
179
|
+
#{email_footer}
|
180
|
+
BODY
|
181
|
+
|
182
|
+
send_email_notification "When! FAILURE for #{args.split.first}...", body
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def send_pending_notification
|
187
|
+
body = <<-BODY
|
188
|
+
Just wanted to let you know that:
|
189
|
+
|
190
|
+
#{args.to_s}
|
191
|
+
|
192
|
+
Is _STILL_ running on #{hostname}, it has not exited. You did want me to let you know didn't you?
|
193
|
+
|
194
|
+
I started the darn thing at #{@start_time.to_s} (#{@start_time.to_i}) and it has taken a total of #{elapsed_time} seconds so far.
|
195
|
+
|
196
|
+
Just thought you'd like to know. I'll continue to keep watching what you asked me to. (boor-ring!)
|
197
|
+
|
198
|
+
Cordially,
|
199
|
+
|
200
|
+
Tellmewhen
|
201
|
+
|
202
|
+
#{email_footer}
|
203
|
+
BODY
|
204
|
+
|
205
|
+
send_email_notification "When! [NOT] I'm _still_ waiting for #{args.split.first}...", body
|
206
|
+
end
|
207
|
+
|
208
|
+
def email_footer
|
209
|
+
return <<-END
|
210
|
+
P.S. stderr says:
|
211
|
+
--
|
212
|
+
#{File.read(@stderr_file)}
|
213
|
+
--
|
214
|
+
|
215
|
+
P.S. stdout says:
|
216
|
+
--
|
217
|
+
#{File.read(@stdout_file)}
|
218
|
+
--
|
219
|
+
END
|
220
|
+
end
|
221
|
+
|
222
|
+
def pid_running? pid
|
223
|
+
lines = `ps #{pid}`.split "\n"
|
224
|
+
lines.count > 1
|
225
|
+
end
|
226
|
+
|
227
|
+
def wait_on_pid args
|
228
|
+
# wait until pid exits
|
229
|
+
while pid_running? @options[:pid]
|
230
|
+
sleep 0.250
|
231
|
+
if elapsed_time > wait_timeout
|
232
|
+
puts "Exceeded timeout #{wait_timeout}, sending 'pending' notificaiton"
|
233
|
+
send_email_notification "When! [NOT] still awaiting pid:#{pid} to exit", <<-END
|
234
|
+
|
235
|
+
I started watching #{pid} on #{hostname} at #{@start_time.to_s} (#{@start_time.to_i}), I've been watching 'em for #{elapsed_time} seconds so far.
|
236
|
+
|
237
|
+
Awaiting it's demise,
|
238
|
+
|
239
|
+
TellMeWhen
|
240
|
+
END
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
@end_time = Time.now
|
245
|
+
|
246
|
+
send_email_notification "When! pid:#{@options[:pid]} has come to its end", <<-END
|
247
|
+
|
248
|
+
I started watching #{@options[:pid]} on #{hostname} at #{@start_time.to_s} (#{@start_time.to_i}), and now, after #{elapsed_time} seconds it has finally gone bellly up. #{@options[:pid]} will rest in peace as of #{@end_time.to_s} (#{@end_time.to_i})
|
249
|
+
|
250
|
+
#{@options[:pid]} will be missed, it was a good little process. :,)
|
251
|
+
|
252
|
+
TellMeWhen
|
253
|
+
END
|
254
|
+
|
255
|
+
end
|
256
|
+
|
257
|
+
def wait_on_file_exists args
|
258
|
+
while !File.exist? @options[:trigger_file]
|
259
|
+
sleep 0.250
|
260
|
+
if elapsed_time > wait_timeout
|
261
|
+
puts "Exceeded timeout #{wait_timeout}, sending 'pending' notificaiton"
|
262
|
+
send_email_notification "When! [NOT] still awaiting #{@options[:trigger_file]} to exist", <<-END
|
263
|
+
|
264
|
+
I started watching for #{@options[:trigger_file]} on #{hostname} at #{@start_time.to_s} (#{@start_time.to_i}), I've been watching for it #{elapsed_time} seconds so far.
|
265
|
+
|
266
|
+
Awaiting it's arrival,
|
267
|
+
|
268
|
+
TellMeWhen
|
269
|
+
END
|
270
|
+
end
|
271
|
+
end
|
272
|
+
@end_time = Time.now
|
273
|
+
|
274
|
+
send_email_notification "When! #{@options[:trigger_file]} now exists.", <<-END
|
275
|
+
|
276
|
+
I started watching for #{@options[:trigger_file]} on #{hostname} at #{@start_time.to_s} (#{@start_time.to_i}), and now, after #{elapsed_time} seconds it has finally shown up as of #{@end_time.to_s} (#{@end_time.to_i})
|
277
|
+
|
278
|
+
What is thy next bidding my master?
|
279
|
+
|
280
|
+
TellMeWhen
|
281
|
+
END
|
282
|
+
end
|
283
|
+
|
284
|
+
def wait_on_file_modified args
|
285
|
+
trigger_file = @options[:trigger_file]
|
286
|
+
initial_mtime = File.mtime trigger_file
|
287
|
+
|
288
|
+
while initial_mtime == File.mtime(trigger_file)
|
289
|
+
sleep 0.250
|
290
|
+
if elapsed_time > wait_timeout
|
291
|
+
puts "Exceeded timeout #{wait_timeout}, sending 'pending' notificaiton"
|
292
|
+
send_email_notification "When! [NOT] still awaiting #{@options[:trigger_file]} to change", <<-END
|
293
|
+
|
294
|
+
I started watching for #{@options[:trigger_file]} to be updated on #{hostname} at #{@start_time.to_s} (#{@start_time.to_i}), I've been watching for it #{elapsed_time} seconds so far.
|
295
|
+
|
296
|
+
Awaiting it's update,
|
297
|
+
|
298
|
+
TellMeWhen
|
299
|
+
END
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
@end_time = Time.now
|
304
|
+
|
305
|
+
send_email_notification "When! #{@options[:trigger_file]} was updated.", <<-END
|
306
|
+
|
307
|
+
I started watching for #{@options[:trigger_file]} to be updated on #{hostname} at #{@start_time.to_s} (#{@start_time.to_i}), and now, after #{elapsed_time} seconds it has finally been modified as of #{@end_time.to_s} (#{@end_time.to_i})
|
308
|
+
|
309
|
+
POSIX is my zen,
|
310
|
+
|
311
|
+
TellMeWhen
|
312
|
+
END
|
313
|
+
end
|
314
|
+
|
315
|
+
def smtp_host
|
316
|
+
@settings["email"]["smtp_host"]
|
317
|
+
end
|
318
|
+
|
319
|
+
def smtp_port
|
320
|
+
@settings["email"]["smtp_port"]
|
321
|
+
end
|
322
|
+
|
323
|
+
def send_email_notification subject, body
|
324
|
+
# optionally send via /usr/bin/mail or sendmail binary if it exists...
|
325
|
+
puts "Sending email: from:#{@settings["email"]["from"]} to:#{@settings["email"]["to"]}"
|
326
|
+
begin
|
327
|
+
Net::SMTP.start(smtp_host, smtp_port) do |smtp|
|
328
|
+
smtp.open_message_stream('from_addr', @settings["email"]["to"].split(',')) do |f|
|
329
|
+
f.puts "From: #{@settings["email"]["from"]}"
|
330
|
+
f.puts "To: #{@settings["email"]["to"]}"
|
331
|
+
f.puts "Subject: #{subject}"
|
332
|
+
f.puts ""
|
333
|
+
f.puts body
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
rescue Errno::ECONNREFUSED => e
|
338
|
+
if File.exist? "/usr/sbin/sendmail"
|
339
|
+
body_file = Tempfile.new("tellmewhen.mail.body")
|
340
|
+
File.open(body_file.path,"w") do |f|
|
341
|
+
f.puts "From: #{@settings["email"]["from"]}"
|
342
|
+
f.puts "To: #{@settings["email"]["to"]}"
|
343
|
+
f.puts "Subject: #{subject}"
|
344
|
+
f.puts ""
|
345
|
+
f.write body
|
346
|
+
end
|
347
|
+
system "/usr/sbin/sendmail -f #{@settings["email"]["from"]} '#{@settings["email"]["to"]}' < #{body_file.path}"
|
348
|
+
elsif File.exist? "/usr/bin/mail"
|
349
|
+
raise "Implement sending via /usr/bin/mail"
|
350
|
+
body_file = Tempfile.new("tellmewhen.mail.body")
|
351
|
+
File.open(body_file.path,"w") do |f|
|
352
|
+
f.puts "From: #{@settings["email"]["from"]}"
|
353
|
+
f.puts "To: #{@settings["email"]["to"]}"
|
354
|
+
f.puts "Subject: #{subject}"
|
355
|
+
f.puts ""
|
356
|
+
f.write body
|
357
|
+
end
|
358
|
+
system "/usr/bin/mail '#{@settings["email"]["to"]}' < #{body_file.path}"
|
359
|
+
else
|
360
|
+
raise "No smtp server (that we can connect to) at #{smtp_host}:#{smtp_port}, could not fall back to /usr/bin/mail either (doesn't exist). Sorry, tried my best."
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
def run args
|
365
|
+
@command = args
|
366
|
+
action = "wait_on_#{@options[:wait_on].to_s}".to_sym
|
367
|
+
if ! self.respond_to? action
|
368
|
+
raise "Error: don't know how to wait on: #{@options[:wait_on]}"
|
369
|
+
end
|
370
|
+
|
371
|
+
@start_time = Time.now
|
372
|
+
if @options[:write_config_to]
|
373
|
+
save_settings @options[:write_config_to]
|
374
|
+
end
|
375
|
+
save_settings
|
376
|
+
self.send action, args
|
377
|
+
return 1
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
if $0 == __FILE__
|
382
|
+
TellMeWhen.main(ARGV)
|
383
|
+
end
|
data/tellmewhen.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
|
4
|
+
SPEC = Gem::Specification.new do |s|
|
5
|
+
s.name = "tellmewhen"
|
6
|
+
s.version = "1.0.0"
|
7
|
+
s.author = "Kyle Burton"
|
8
|
+
s.email = "kyle.burton@gmail.com"
|
9
|
+
s.platform = Gem::Platform::RUBY
|
10
|
+
s.description = <<DESC
|
11
|
+
Notifys you when another command completes (via email).
|
12
|
+
|
13
|
+
./tellmewhen 'sleep 3; ls' # await a command to complete
|
14
|
+
./tellmewhen -p 12345 # await a pid to exit
|
15
|
+
./tellmewhen -e some-file.txt # await existance
|
16
|
+
./tellmewhen -m some-file.txt # await update
|
17
|
+
|
18
|
+
Tells you when, how long and sends you the output (for commands it runs).
|
19
|
+
|
20
|
+
DESC
|
21
|
+
s.summary = "Notifys you when another command completes (via email)."
|
22
|
+
# s.rubyforge_project = "typrtail"
|
23
|
+
s.homepage = "http://github.com/kyleburton/tellmewhen"
|
24
|
+
s.files = Dir.glob("**/*")
|
25
|
+
s.executables << "tellmewhen"
|
26
|
+
s.require_path = "bin"
|
27
|
+
s.has_rdoc = false
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tellmewhen
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Kyle Burton
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-01-25 00:00:00 -05:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: |+
|
23
|
+
Notifys you when another command completes (via email).
|
24
|
+
|
25
|
+
./tellmewhen 'sleep 3; ls' # await a command to complete
|
26
|
+
./tellmewhen -p 12345 # await a pid to exit
|
27
|
+
./tellmewhen -e some-file.txt # await existance
|
28
|
+
./tellmewhen -m some-file.txt # await update
|
29
|
+
|
30
|
+
Tells you when, how long and sends you the output (for commands it runs).
|
31
|
+
|
32
|
+
email: kyle.burton@gmail.com
|
33
|
+
executables:
|
34
|
+
- tellmewhen
|
35
|
+
extensions: []
|
36
|
+
|
37
|
+
extra_rdoc_files: []
|
38
|
+
|
39
|
+
files:
|
40
|
+
- bin/tellmewhen
|
41
|
+
- Rakefile
|
42
|
+
- README.textile
|
43
|
+
- tellmewhen.gemspec
|
44
|
+
has_rdoc: true
|
45
|
+
homepage: http://github.com/kyleburton/tellmewhen
|
46
|
+
licenses: []
|
47
|
+
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
|
51
|
+
require_paths:
|
52
|
+
- bin
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
hash: 3
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
hash: 3
|
68
|
+
segments:
|
69
|
+
- 0
|
70
|
+
version: "0"
|
71
|
+
requirements: []
|
72
|
+
|
73
|
+
rubyforge_project:
|
74
|
+
rubygems_version: 1.3.7
|
75
|
+
signing_key:
|
76
|
+
specification_version: 3
|
77
|
+
summary: Notifys you when another command completes (via email).
|
78
|
+
test_files: []
|
79
|
+
|