directory_watcher 0.1.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/lib/directory_watcher.rb +510 -0
- metadata +54 -0
@@ -0,0 +1,510 @@
|
|
1
|
+
#
|
2
|
+
# = directory_watcher.rb
|
3
|
+
#
|
4
|
+
# See DirectoryWatcher for detailed documentation and usage.
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'observer'
|
8
|
+
|
9
|
+
#
|
10
|
+
# == Synopsis
|
11
|
+
#
|
12
|
+
# A class for watching files within a directory and generating events when
|
13
|
+
# those files change.
|
14
|
+
#
|
15
|
+
# == Details
|
16
|
+
#
|
17
|
+
# A directory watcher is an +Observable+ object that sends events to
|
18
|
+
# registered observers when file changes are detected within the directory
|
19
|
+
# being watched.
|
20
|
+
#
|
21
|
+
# The directory watcher operates by scanning the directory at some interval
|
22
|
+
# and creating a list of the files it finds. File events are detected by
|
23
|
+
# comparing the current file list with the file list from the previous scan
|
24
|
+
# interval. Three types of events are supported -- *added*, *modified*, and
|
25
|
+
# *removed*.
|
26
|
+
#
|
27
|
+
# An added event is generated when the file appears in the current file
|
28
|
+
# list but not in the previous scan interval file list. A removed event is
|
29
|
+
# generated when the file appears in the previous scan interval file list
|
30
|
+
# but not in the current file list. A modified event is generated when the
|
31
|
+
# file appears in the current and the previous interval file list, but the
|
32
|
+
# file modification time or the file size differs between the two lists.
|
33
|
+
#
|
34
|
+
# The file events are collected into an array, and all registered observers
|
35
|
+
# receive all file events for each scan interval. It is up to the individual
|
36
|
+
# observers to filter the events they are interested in.
|
37
|
+
#
|
38
|
+
# === File Selection
|
39
|
+
#
|
40
|
+
# The directory watcher uses glob patterns to select the files to scan. The
|
41
|
+
# default glob pattern will select all regular files in the directory of
|
42
|
+
# interest '*'.
|
43
|
+
#
|
44
|
+
# Here are a few useful glob examples:
|
45
|
+
#
|
46
|
+
# '*' => all files in the current directory
|
47
|
+
# '**/*' => all files in all subdirectories
|
48
|
+
# '**/*.rb' => all ruby files
|
49
|
+
# 'ext/**/*.{h,c}' => all C source code files
|
50
|
+
#
|
51
|
+
# *Note*: file events will never be generated for directories. Only regular
|
52
|
+
# files are included in the file scan.
|
53
|
+
#
|
54
|
+
# === Stable Files
|
55
|
+
#
|
56
|
+
# A fourth file event is supported but not enabled by default -- the
|
57
|
+
# *stable* event. This event is generated after a file has been added or
|
58
|
+
# modified and then remains unchanged for a certain number of scan
|
59
|
+
# intervals.
|
60
|
+
#
|
61
|
+
# To enable the generation of this event the +stable+ count must be
|
62
|
+
# configured. This is the number of scan intervals a file must remain
|
63
|
+
# unchanged (based modification time and file size) before it is considered
|
64
|
+
# stable.
|
65
|
+
#
|
66
|
+
# To disable this event the +stable+ count should be set to +nil+.
|
67
|
+
#
|
68
|
+
# == Usage
|
69
|
+
#
|
70
|
+
# Learn by Doing -- here are a few different ways to configure and use a
|
71
|
+
# directory watcher.
|
72
|
+
#
|
73
|
+
# === Basic
|
74
|
+
#
|
75
|
+
# This basic recipe will watch all files in the current directory and
|
76
|
+
# generate the three default events. We'll register an observer that simply
|
77
|
+
# prints the events to standard out.
|
78
|
+
#
|
79
|
+
# require 'directory_watcher'
|
80
|
+
#
|
81
|
+
# dw = DirectoryWatcher.new '.'
|
82
|
+
# dw.add_observer {|*args| args.each {|event| puts event}}
|
83
|
+
#
|
84
|
+
# dw.start
|
85
|
+
# gets # when the user hits "enter" the script will terminate
|
86
|
+
# dw.stop
|
87
|
+
#
|
88
|
+
# === Suppress Initial "added" Events
|
89
|
+
#
|
90
|
+
# This little twist will suppress the initial "added" events that are
|
91
|
+
# generated the first time the directory is scanned. This is done by
|
92
|
+
# pre-loading the watcher with files -- i.e. telling the watcher to scan for
|
93
|
+
# files before actually starting the scan loop.
|
94
|
+
#
|
95
|
+
# require 'directory_watcher'
|
96
|
+
#
|
97
|
+
# dw = DirectoryWatcher.new '.', :pre_load => true
|
98
|
+
# dw.glob = '**/*.rb'
|
99
|
+
# dw.add_observer {|*args| args.each {|event| puts event}}
|
100
|
+
#
|
101
|
+
# dw.start
|
102
|
+
# gets # when the user hits "enter" the script will terminate
|
103
|
+
# dw.stop
|
104
|
+
#
|
105
|
+
# There is one catch with this recipe. The glob pattern must be specified
|
106
|
+
# before the pre-load takes place. The glob pattern can be given as an
|
107
|
+
# option to the constructor:
|
108
|
+
#
|
109
|
+
# dw = DirectoryWatcher.new '.', :glob => '**/*.rb', :pre_load => true
|
110
|
+
#
|
111
|
+
# The other option is to use the reset method:
|
112
|
+
#
|
113
|
+
# dw = DirectoryWatcher.new '.'
|
114
|
+
# dw.glob = '**/*.rb'
|
115
|
+
# dw.reset true # the +true+ flag causes the watcher to pre-load
|
116
|
+
# # the files
|
117
|
+
#
|
118
|
+
# === Generate "stable" Events
|
119
|
+
#
|
120
|
+
# In order to generate stable events, the stable count must be specified. In
|
121
|
+
# this example the interval is set to 5.0 seconds and the stable count is
|
122
|
+
# set to 2. Stable events will only be generated for files after they have
|
123
|
+
# remain unchanged for 10 seconds (5.0 * 2).
|
124
|
+
#
|
125
|
+
# require 'directory_watcher'
|
126
|
+
#
|
127
|
+
# dw = DirectoryWatcher.new '.', :glob => '**/*.rb'
|
128
|
+
# dw.interval = 5.0
|
129
|
+
# dw.stable = 2
|
130
|
+
# dw.add_observer {|*args| args.each {|event| puts event}}
|
131
|
+
#
|
132
|
+
# dw.start
|
133
|
+
# gets # when the user hits "enter" the script will terminate
|
134
|
+
# dw.stop
|
135
|
+
#
|
136
|
+
# == Contact
|
137
|
+
#
|
138
|
+
# A lot of discussion happens about Ruby in general on the ruby-talk
|
139
|
+
# mailing list (http://www.ruby-lang.org/en/ml.html), and you can ask
|
140
|
+
# any questions you might have there. I monitor the list, as do many
|
141
|
+
# other helpful Rubyists, and you're sure to get a quick answer. Of
|
142
|
+
# course, you're also welcome to email me (Tim Pease) directly at the
|
143
|
+
# at tim.pease@gmail.com, and I'll do my best to help you out.
|
144
|
+
#
|
145
|
+
# (the above paragraph was blatantly stolen from Nathaniel Talbott's
|
146
|
+
# Test::Unit documentation)
|
147
|
+
#
|
148
|
+
# == Author
|
149
|
+
#
|
150
|
+
# Tim Pease
|
151
|
+
#
|
152
|
+
class DirectoryWatcher
|
153
|
+
include Observable
|
154
|
+
|
155
|
+
#
|
156
|
+
# An +Event+ structure contains the _type_ of the event and the file _path_
|
157
|
+
# to which the event pertains. The type can be one of the following:
|
158
|
+
#
|
159
|
+
# :added => file has been added to the directory
|
160
|
+
# :modified => file has been modified (either mtime or size or both
|
161
|
+
# have changed)
|
162
|
+
# :removed => file has been removed from the directory
|
163
|
+
# :stable => file has stabilized since being added or modified
|
164
|
+
#
|
165
|
+
Event = Struct.new :type, :path
|
166
|
+
|
167
|
+
FileInfo = Struct.new :mtime, :size, :stable # :nodoc:
|
168
|
+
|
169
|
+
#
|
170
|
+
# call-seq:
|
171
|
+
# DirectoryWatcher.new( directory, options )
|
172
|
+
#
|
173
|
+
# Create a new +DirectoryWatcher+ that will generate events when file
|
174
|
+
# changes are detected in the given _directory_. If the _directory_ does
|
175
|
+
# not exist, it will be created. The following options can be passed to
|
176
|
+
# this method:
|
177
|
+
#
|
178
|
+
# :glob => '*' file glob pattern to restrict scanning
|
179
|
+
# :interval => 30.0 the directory scan interval (in seconds)
|
180
|
+
# :stable => nil the number of intervals a file must remain
|
181
|
+
# unchanged for it to be considered "stable"
|
182
|
+
# :pre_load => false setting this option to true will pre-load the
|
183
|
+
# file list effectively skipping the initial
|
184
|
+
# round of file added events that would normally
|
185
|
+
# be generated (glob pattern must also be
|
186
|
+
# specified otherwise odd things will happen)
|
187
|
+
#
|
188
|
+
# The default glob pattern will scan all files in the configured directory.
|
189
|
+
# Setting the :stable option to +nil+ will prevent stable events from being
|
190
|
+
# generated.
|
191
|
+
#
|
192
|
+
def initialize( directory, opts = {} )
|
193
|
+
@dir = directory
|
194
|
+
|
195
|
+
if Kernel.test(?e, @dir)
|
196
|
+
unless Kernel.test(?d, @dir)
|
197
|
+
raise ArgumentError, "'#{dir}' is not a directory"
|
198
|
+
end
|
199
|
+
else
|
200
|
+
Dir.create @dir
|
201
|
+
end
|
202
|
+
|
203
|
+
self.glob = opts[:glob] || '*'
|
204
|
+
self.interval = opts[:interval] || 30
|
205
|
+
self.stable = opts[:stable] || nil
|
206
|
+
|
207
|
+
@files = (opts[:pre_load] ? scan_files : Hash.new)
|
208
|
+
@events = []
|
209
|
+
end
|
210
|
+
|
211
|
+
#
|
212
|
+
# call-seq:
|
213
|
+
# add_observer( observer )
|
214
|
+
# add_observer {|*args| block}
|
215
|
+
#
|
216
|
+
# Adds the given _observer_ as an observer on this directory watcher. The
|
217
|
+
# _observer_ will now receive file events when they are generated.
|
218
|
+
#
|
219
|
+
# Optionally, a block can be passed as the observer. The block will be
|
220
|
+
# executed with the file events passed as the arguments. A reference to the
|
221
|
+
# underlying +Proc+ object will be returned for use with the
|
222
|
+
# +delete_observer+ method.
|
223
|
+
#
|
224
|
+
def add_observer( observer = nil, &block )
|
225
|
+
unless block.nil?
|
226
|
+
observer = block.to_proc
|
227
|
+
class << observer
|
228
|
+
alias_method :update, :call
|
229
|
+
end
|
230
|
+
end
|
231
|
+
super observer
|
232
|
+
end
|
233
|
+
|
234
|
+
#
|
235
|
+
# call-seq:
|
236
|
+
# glob = '*'
|
237
|
+
# glob = ['lib/**/*.rb', 'test/**/*.rb']
|
238
|
+
#
|
239
|
+
# Sets the glob pattern that will be used when scanning the directory for
|
240
|
+
# files. A single glob pattern can be given or an array of glob patterns.
|
241
|
+
#
|
242
|
+
def glob=( val )
|
243
|
+
@glob = case val
|
244
|
+
when String: [File.join(@dir, val)]
|
245
|
+
when Array: val.flatten.map! {|g| File.join(@dir, g)}
|
246
|
+
else
|
247
|
+
raise(ArgumentError,
|
248
|
+
'expecting a glob pattern or an array of glob patterns')
|
249
|
+
end
|
250
|
+
@glob.uniq!
|
251
|
+
val
|
252
|
+
end
|
253
|
+
attr_reader :glob
|
254
|
+
|
255
|
+
#
|
256
|
+
# call-seq:
|
257
|
+
# interval = 30.0
|
258
|
+
#
|
259
|
+
# Sets the directory scan interval. The directory will be scanned every
|
260
|
+
# _interval_ seconds for changes to files matching the glob pattern.
|
261
|
+
# Raises +ArgumentError+ if the interval is zero or negative.
|
262
|
+
#
|
263
|
+
def interval=( val )
|
264
|
+
val = Float(val)
|
265
|
+
raise ArgumentError, "interval must be greater than zero" if val <= 0
|
266
|
+
@interval = Float(val)
|
267
|
+
end
|
268
|
+
attr_reader :interval
|
269
|
+
|
270
|
+
#
|
271
|
+
# call-seq:
|
272
|
+
# stable = 2
|
273
|
+
#
|
274
|
+
# Sets the number of intervals a file must remain unchanged before it is
|
275
|
+
# considered "stable". When this condition is met, a stable event is
|
276
|
+
# generated for the file. If stable is set to +nil+ then stable events
|
277
|
+
# will not be generated.
|
278
|
+
#
|
279
|
+
# A stable event will be generated once for a file. Another stable event
|
280
|
+
# will only be generated after the file has been modified and then remains
|
281
|
+
# unchanged for _stable_ intervals.
|
282
|
+
#
|
283
|
+
# Example:
|
284
|
+
#
|
285
|
+
# dw = DirectoryWatcher.new( '/tmp', :glob => 'swap.*' )
|
286
|
+
# dw.interval = 15.0
|
287
|
+
# dw.stable = 4
|
288
|
+
#
|
289
|
+
# In this example, a directory watcher is configured to look for swap files
|
290
|
+
# in the /tmp directory. Stable events will be generated every 4 scan
|
291
|
+
# intervals iff a swap remains unchanged for that time. In this case the
|
292
|
+
# time is 60 seconds (15.0 * 4).
|
293
|
+
#
|
294
|
+
def stable=( val )
|
295
|
+
if val.nil?
|
296
|
+
@stable = nil
|
297
|
+
return
|
298
|
+
end
|
299
|
+
|
300
|
+
val = Integer(val)
|
301
|
+
raise ArgumentError, "stable must be greater than zero" if val <= 0
|
302
|
+
@stable = val
|
303
|
+
end
|
304
|
+
attr_reader :stable
|
305
|
+
|
306
|
+
#
|
307
|
+
# call-seq:
|
308
|
+
# running?
|
309
|
+
#
|
310
|
+
# Returns +true+ if the directory watcher is currently running. Returns
|
311
|
+
# +false+ if this is not the case.
|
312
|
+
#
|
313
|
+
def running?
|
314
|
+
!@thread.nil?
|
315
|
+
end
|
316
|
+
|
317
|
+
#
|
318
|
+
# call-seq:
|
319
|
+
# start
|
320
|
+
#
|
321
|
+
# Start the directory watcher scanning thread. If the directory watcher is
|
322
|
+
# already running, this method will return without taking any action.
|
323
|
+
#
|
324
|
+
def start
|
325
|
+
return if running?
|
326
|
+
|
327
|
+
@stop = false
|
328
|
+
@thread = Thread.new(self) {|dw| dw.send :run}
|
329
|
+
self
|
330
|
+
end
|
331
|
+
|
332
|
+
#
|
333
|
+
# call-seq:
|
334
|
+
# stop
|
335
|
+
#
|
336
|
+
# Stop the directory watcher scanning thread. If the directory watcher is
|
337
|
+
# already stopped, this method will return without taking any action.
|
338
|
+
#
|
339
|
+
def stop
|
340
|
+
return unless running?
|
341
|
+
|
342
|
+
@stop = true
|
343
|
+
@thread.wakeup if @thread.status == 'sleep'
|
344
|
+
@thread.join
|
345
|
+
@thread = nil
|
346
|
+
self
|
347
|
+
end
|
348
|
+
|
349
|
+
#
|
350
|
+
# call-seq:
|
351
|
+
# reset( pre_load = false )
|
352
|
+
#
|
353
|
+
# Reset the directory watcher state by clearing the stored file list. If
|
354
|
+
# the directory watcher is running, it will be stopped, the file list
|
355
|
+
# cleared, and then restarted. Passing +true+ to this method will cause
|
356
|
+
# the file list to be pre-loaded after it has been cleared effectively
|
357
|
+
# skipping the initial round of file added events that would normally be
|
358
|
+
# generated.
|
359
|
+
#
|
360
|
+
def reset( pre_load = false )
|
361
|
+
was_running = running?
|
362
|
+
|
363
|
+
stop if was_running
|
364
|
+
@files = (pre_load ? scan_files : Hash.new)
|
365
|
+
start if was_running
|
366
|
+
end
|
367
|
+
|
368
|
+
private
|
369
|
+
#
|
370
|
+
# call-seq:
|
371
|
+
# scan_files
|
372
|
+
#
|
373
|
+
# Using the configured glob pattern, scan the directory for all files and
|
374
|
+
# return a hash with the filenames as keys and +FileInfo+ objects as the
|
375
|
+
# values. The +FileInfo+ objects contain the mtime and size of the file.
|
376
|
+
#
|
377
|
+
def scan_files
|
378
|
+
files = {}
|
379
|
+
@glob.each do |glob|
|
380
|
+
Dir.glob(glob).each do |fn|
|
381
|
+
begin
|
382
|
+
if Kernel.test(?f, fn)
|
383
|
+
files[fn] = FileInfo.new(File.mtime(fn), File.size(fn))
|
384
|
+
end
|
385
|
+
rescue SystemCallError; end
|
386
|
+
end
|
387
|
+
end
|
388
|
+
files
|
389
|
+
end
|
390
|
+
|
391
|
+
#
|
392
|
+
# call-seq:
|
393
|
+
# run
|
394
|
+
#
|
395
|
+
# Calling this method will enter the directory watcher's run loop. The
|
396
|
+
# calling thread will not return until the +stop+ method is called.
|
397
|
+
#
|
398
|
+
# The run loop is responsible for scanning the directory for file changes,
|
399
|
+
# and then dispatching events to registered listeners.
|
400
|
+
#
|
401
|
+
def run
|
402
|
+
until @stop
|
403
|
+
start = Time.now.to_f
|
404
|
+
|
405
|
+
files = scan_files
|
406
|
+
keys = [files.keys, @files.keys]
|
407
|
+
common = keys.first & keys.last
|
408
|
+
|
409
|
+
find_added(files, *keys)
|
410
|
+
find_modified(files, common)
|
411
|
+
find_removed(*keys)
|
412
|
+
|
413
|
+
notify_observers
|
414
|
+
@files = files
|
415
|
+
|
416
|
+
nap_time = @interval - (Time.now.to_f - start)
|
417
|
+
sleep nap_time if nap_time > 0
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
#
|
422
|
+
# call-seq:
|
423
|
+
# find_added( files, cur, prev )
|
424
|
+
#
|
425
|
+
# Taking the list of current files, _cur_, and the list of files found
|
426
|
+
# previously, _prev_, figure out which files have been added and generate
|
427
|
+
# a new file added event for each. The events are returned as an array
|
428
|
+
# from this method. If no files have been added, the returned array is
|
429
|
+
# empty.
|
430
|
+
#
|
431
|
+
def find_added( files, cur, prev )
|
432
|
+
added = cur - prev
|
433
|
+
added.each do |fn|
|
434
|
+
files[fn].stable = @stable
|
435
|
+
@events << Event.new(:added, fn)
|
436
|
+
end
|
437
|
+
self
|
438
|
+
end
|
439
|
+
|
440
|
+
#
|
441
|
+
# call-seq:
|
442
|
+
# find_removed( cur, prev )
|
443
|
+
#
|
444
|
+
# Taking the list of current files, _cur_, and the list of files found
|
445
|
+
# previously, _prev_, figure out which files have been removed and
|
446
|
+
# generate a new file removed event for each. The events are returned as
|
447
|
+
# an array from this method. If no files have been removed, the returned
|
448
|
+
# array is empty.
|
449
|
+
#
|
450
|
+
def find_removed( cur, prev )
|
451
|
+
removed = prev - cur
|
452
|
+
removed.each {|fn| @events << Event.new(:removed, fn)}
|
453
|
+
self
|
454
|
+
end
|
455
|
+
|
456
|
+
#
|
457
|
+
# call-seq:
|
458
|
+
# find_modified( files, common )
|
459
|
+
#
|
460
|
+
# Taking the list of _common_ files (those that exist in the current file
|
461
|
+
# list and in the previous file list) determine if any have been modified.
|
462
|
+
# Generate a new file modified event for each modified file. Also, by
|
463
|
+
# looking at the stable count in the _files_ hash, figure out if any files
|
464
|
+
# have become stable since being added or modified. Generate a new stable
|
465
|
+
# event for each stabilized file. The events are returned as an array from
|
466
|
+
# this method. If there are no events, the array is empty.
|
467
|
+
#
|
468
|
+
def find_modified( files, common )
|
469
|
+
common.each do |key|
|
470
|
+
cur, prev = files[key], @files[key]
|
471
|
+
|
472
|
+
# if the modification time or the file size differs from the last
|
473
|
+
# time it was seen, then create a :modified event
|
474
|
+
if cur.mtime != prev.mtime or cur.size != prev.size
|
475
|
+
@events << Event.new(:modified, key)
|
476
|
+
cur.stable = @stable
|
477
|
+
|
478
|
+
# otherwise, if the count is not nil see if we need to create a
|
479
|
+
# :stable event
|
480
|
+
elsif !prev.stable.nil?
|
481
|
+
cur.stable = prev.stable - 1
|
482
|
+
if cur.stable == 0
|
483
|
+
@events << Event.new(:stable, key)
|
484
|
+
cur.stable = nil
|
485
|
+
end
|
486
|
+
end
|
487
|
+
end
|
488
|
+
self
|
489
|
+
end
|
490
|
+
|
491
|
+
#
|
492
|
+
# call-seq:
|
493
|
+
# notify_observers
|
494
|
+
#
|
495
|
+
# If there are queued files events, then invoke the update method of each
|
496
|
+
# registered observer in turn passing the list of file events to each.
|
497
|
+
# The file events array is cleared at the end of this method call.
|
498
|
+
#
|
499
|
+
def notify_observers
|
500
|
+
unless @events.empty? or !@observer_peers
|
501
|
+
@observer_peers.dup.each do |observer|
|
502
|
+
begin; observer.update(*@events); rescue Exception; end
|
503
|
+
end
|
504
|
+
end
|
505
|
+
@events.clear
|
506
|
+
end
|
507
|
+
|
508
|
+
end # class DirectoryWatcher
|
509
|
+
|
510
|
+
# EOF
|
metadata
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.0
|
3
|
+
specification_version: 1
|
4
|
+
name: directory_watcher
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.1.0
|
7
|
+
date: 2006-11-10 00:00:00 -07:00
|
8
|
+
summary: A class for watching files within a directory and generating events when those files change
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: tim.pease@gmail.com
|
12
|
+
homepage:
|
13
|
+
rubyforge_project: codeforpeople.com
|
14
|
+
description: The directory watcher operates by scanning a directory at some interval and generating a list of files based on a user supplied glob pattern. As the file list changes from one interval to the next, events are generated and dispatched to registered observers. Three types of events are supported -- added, modified, and removed.
|
15
|
+
autorequire:
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- Tim Pease
|
31
|
+
files:
|
32
|
+
- lib/directory_watcher.rb
|
33
|
+
test_files: []
|
34
|
+
|
35
|
+
rdoc_options: []
|
36
|
+
|
37
|
+
extra_rdoc_files: []
|
38
|
+
|
39
|
+
executables: []
|
40
|
+
|
41
|
+
extensions: []
|
42
|
+
|
43
|
+
requirements: []
|
44
|
+
|
45
|
+
dependencies:
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: hoe
|
48
|
+
version_requirement:
|
49
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.1.3
|
54
|
+
version:
|